quantnodes 3.0.0__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.
- QuantNodes/__init__.py +15 -0
- QuantNodes/__main__.py +14 -0
- QuantNodes/agent/__init__.py +158 -0
- QuantNodes/agent/agents/__init__.py +13 -0
- QuantNodes/agent/agents/definition.py +180 -0
- QuantNodes/agent/agents/manager.py +73 -0
- QuantNodes/agent/config/__init__.py +34 -0
- QuantNodes/agent/config/executor.py +958 -0
- QuantNodes/agent/config/loader.py +427 -0
- QuantNodes/agent/config/templates/bollinger_bands.yaml +84 -0
- QuantNodes/agent/config/templates/dual_ma.yaml +72 -0
- QuantNodes/agent/config/templates/empty.yaml +56 -0
- QuantNodes/agent/config/templates/mean_reversion.yaml +47 -0
- QuantNodes/agent/config/templates/mean_reversion_zscore.yaml +90 -0
- QuantNodes/agent/config/templates/momentum.yaml +81 -0
- QuantNodes/agent/config/templates/momentum_breakout.yaml +84 -0
- QuantNodes/agent/config/templates/rsi_strategy.yaml +72 -0
- QuantNodes/agent/config/templates/volume_price.yaml +86 -0
- QuantNodes/agent/config/types.py +156 -0
- QuantNodes/agent/config_mapper.py +293 -0
- QuantNodes/agent/core/__init__.py +19 -0
- QuantNodes/agent/core/dream.py +47 -0
- QuantNodes/agent/core/quant_dream.py +274 -0
- QuantNodes/agent/cron_jobs.py +314 -0
- QuantNodes/agent/nanobot_bridge.py +242 -0
- QuantNodes/agent/permission/__init__.py +30 -0
- QuantNodes/agent/permission/defaults.py +36 -0
- QuantNodes/agent/permission/evaluate.py +41 -0
- QuantNodes/agent/permission/models.py +59 -0
- QuantNodes/agent/permission/service.py +133 -0
- QuantNodes/agent/providers/__init__.py +11 -0
- QuantNodes/agent/providers/base.py +102 -0
- QuantNodes/agent/providers/quantnodes.py +610 -0
- QuantNodes/agent/providers/rate_limiter.py +326 -0
- QuantNodes/agent/providers/registry.py +163 -0
- QuantNodes/agent/skills/__init__.py +20 -0
- QuantNodes/agent/skills/base.py +118 -0
- QuantNodes/agent/skills/bridge.py +73 -0
- QuantNodes/agent/skills/factor/__init__.py +14 -0
- QuantNodes/agent/skills/factor/correlation.py +99 -0
- QuantNodes/agent/skills/factor/group_backtest.py +114 -0
- QuantNodes/agent/skills/factor/ic_analysis.py +106 -0
- QuantNodes/agent/skills/loader.py +107 -0
- QuantNodes/agent/skills/registry.py +105 -0
- QuantNodes/agent/skills/strategy/__init__.py +16 -0
- QuantNodes/agent/skills/strategy/bollinger.py +86 -0
- QuantNodes/agent/skills/strategy/dual_ma.py +82 -0
- QuantNodes/agent/skills/strategy/momentum.py +74 -0
- QuantNodes/agent/skills/strategy/rsi_reversal.py +99 -0
- QuantNodes/agent/skills_quant/__init__.py +14 -0
- QuantNodes/agent/skills_quant/backtest-analyze/SKILL.md +42 -0
- QuantNodes/agent/skills_quant/config-driven/SKILL.md +72 -0
- QuantNodes/agent/skills_quant/factor-research/SKILL.md +40 -0
- QuantNodes/agent/skills_quant/quant-dream/SKILL.md +55 -0
- QuantNodes/agent/skills_quant/risk-management/SKILL.md +45 -0
- QuantNodes/agent/skills_quant/strategy-design/SKILL.md +43 -0
- QuantNodes/agent/templates/__init__.py +4 -0
- QuantNodes/agent/tools/__init__.py +173 -0
- QuantNodes/agent/tools/_workspace.py +51 -0
- QuantNodes/agent/tools/alpha_backtest.py +328 -0
- QuantNodes/agent/tools/alpha_evaluate.py +493 -0
- QuantNodes/agent/tools/backtest.py +226 -0
- QuantNodes/agent/tools/base.py +133 -0
- QuantNodes/agent/tools/code_search.py +207 -0
- QuantNodes/agent/tools/config_backtest.py +401 -0
- QuantNodes/agent/tools/context.py +97 -0
- QuantNodes/agent/tools/dream_skill.py +77 -0
- QuantNodes/agent/tools/echo.py +38 -0
- QuantNodes/agent/tools/factor.py +231 -0
- QuantNodes/agent/tools/file_ops.py +201 -0
- QuantNodes/agent/tools/git_ops.py +190 -0
- QuantNodes/agent/tools/operator_lookup.py +218 -0
- QuantNodes/agent/tools/output_truncation.py +77 -0
- QuantNodes/agent/tools/path_check.py +43 -0
- QuantNodes/agent/tools/pipeline.py +62 -0
- QuantNodes/agent/tools/registry.py +150 -0
- QuantNodes/agent/tools/sandbox.py +62 -0
- QuantNodes/agent/tools/shell_safety.py +63 -0
- QuantNodes/agent/tools/strategy.py +106 -0
- QuantNodes/agent/tools/task.py +171 -0
- QuantNodes/agent/tools/web_fetch.py +142 -0
- QuantNodes/agent/tools/web_search.py +114 -0
- QuantNodes/agent/tools/wiki.py +370 -0
- QuantNodes/agent/utils/__init__.py +11 -0
- QuantNodes/agent/utils/helpers.py +43 -0
- QuantNodes/agent/utils/prompt_templates.py +30 -0
- QuantNodes/agent/workflows/__init__.py +20 -0
- QuantNodes/agent/workflows/implementations/__init__.py +8 -0
- QuantNodes/agent/workflows/implementations/alpha_gpt.py +508 -0
- QuantNodes/agent/workflows/implementations/mcts.py +442 -0
- QuantNodes/agent/workflows/parsers.py +44 -0
- QuantNodes/agent/workflows/registry.py +119 -0
- QuantNodes/agent/workflows/step_agent.py +219 -0
- QuantNodes/agent/workflows/tool.py +198 -0
- QuantNodes/ai/__init__.py +93 -0
- QuantNodes/ai/llm/__init__.py +75 -0
- QuantNodes/ai/llm/base.py +233 -0
- QuantNodes/ai/llm/decorators.py +281 -0
- QuantNodes/ai/llm/gateway.py +571 -0
- QuantNodes/ai/llm/null.py +76 -0
- QuantNodes/ai/llm/openai.py +435 -0
- QuantNodes/ai/optimizer.py +405 -0
- QuantNodes/ai/prompts/__init__.py +229 -0
- QuantNodes/ai/sandbox.py +371 -0
- QuantNodes/ai/sandbox_pandas_bridge.py +150 -0
- QuantNodes/ai/strategy_gen.py +396 -0
- QuantNodes/backtest/__init__.py +64 -0
- QuantNodes/backtest/backtest_node.py +188 -0
- QuantNodes/backtest/broker_node.py +378 -0
- QuantNodes/backtest/config_runner.py +397 -0
- QuantNodes/backtest/config_strategy.py +64 -0
- QuantNodes/backtest/risk_node.py +360 -0
- QuantNodes/backtest/strategy_node.py +268 -0
- QuantNodes/cache_node/__init__.py +19 -0
- QuantNodes/cache_node/base.py +244 -0
- QuantNodes/cache_node/cache_store.py +99 -0
- QuantNodes/cache_node/metadata.py +100 -0
- QuantNodes/cli/__init__.py +109 -0
- QuantNodes/cli/_helpers.py +511 -0
- QuantNodes/cli/command.py +110 -0
- QuantNodes/cli/commands/__init__.py +69 -0
- QuantNodes/cli/commands/agent.py +158 -0
- QuantNodes/cli/commands/alpha.py +951 -0
- QuantNodes/cli/commands/chat.py +38 -0
- QuantNodes/cli/commands/evolve.py +120 -0
- QuantNodes/cli/commands/factor.py +569 -0
- QuantNodes/cli/commands/init.py +190 -0
- QuantNodes/cli/commands/run.py +259 -0
- QuantNodes/cli/commands/serve.py +398 -0
- QuantNodes/cli/commands/version.py +120 -0
- QuantNodes/cli/enhanced.py +146 -0
- QuantNodes/conf_node/__init__.py +37 -0
- QuantNodes/conf_node/base.py +120 -0
- QuantNodes/conf_node/env_config.py +132 -0
- QuantNodes/conf_node/ini_config.py +70 -0
- QuantNodes/conf_node/json_config.py +69 -0
- QuantNodes/conf_node/yaml_config.py +78 -0
- QuantNodes/constants.py +17 -0
- QuantNodes/core/__init__.py +196 -0
- QuantNodes/core/_lookback_helpers.py +49 -0
- QuantNodes/core/ast_parser.py +198 -0
- QuantNodes/core/base.py +61 -0
- QuantNodes/core/cache_manager.py +344 -0
- QuantNodes/core/cache_utils.py +150 -0
- QuantNodes/core/cond_builder.py +53 -0
- QuantNodes/core/config.py +170 -0
- QuantNodes/core/constants.py +48 -0
- QuantNodes/core/control.py +412 -0
- QuantNodes/core/data_preprocessing.py +453 -0
- QuantNodes/core/data_source.py +46 -0
- QuantNodes/core/events.py +178 -0
- QuantNodes/core/evolution/__init__.py +22 -0
- QuantNodes/core/evolution/loop.py +583 -0
- QuantNodes/core/evolution/operators.py +289 -0
- QuantNodes/core/evolution/settings.py +44 -0
- QuantNodes/core/expression.py +841 -0
- QuantNodes/core/feedback/__init__.py +38 -0
- QuantNodes/core/feedback/channels.py +182 -0
- QuantNodes/core/feedback/collector.py +91 -0
- QuantNodes/core/feedback/dataclass.py +239 -0
- QuantNodes/core/feedback/llm_judge.py +138 -0
- QuantNodes/core/knowledge/__init__.py +69 -0
- QuantNodes/core/knowledge/knowledge_base.py +217 -0
- QuantNodes/core/knowledge/lineage_compress.py +196 -0
- QuantNodes/core/knowledge/lineage_expand.py +123 -0
- QuantNodes/core/knowledge/metrics/__init__.py +43 -0
- QuantNodes/core/knowledge/metrics/evaluator.py +176 -0
- QuantNodes/core/knowledge/metrics/metrics.py +220 -0
- QuantNodes/core/knowledge/rag_prompt.py +196 -0
- QuantNodes/core/knowledge/retriever.py +209 -0
- QuantNodes/core/lambda_node.py +81 -0
- QuantNodes/core/monitoring/__init__.py +22 -0
- QuantNodes/core/monitoring/collector.py +292 -0
- QuantNodes/core/monitoring/dashboard.py +365 -0
- QuantNodes/core/node.py +375 -0
- QuantNodes/core/pandas_utils.py +504 -0
- QuantNodes/core/parallel/__init__.py +15 -0
- QuantNodes/core/parallel/worker.py +140 -0
- QuantNodes/core/parallel/worker_process.py +265 -0
- QuantNodes/core/path_utils.py +73 -0
- QuantNodes/core/pipeline.py +328 -0
- QuantNodes/core/plugin.py +135 -0
- QuantNodes/core/quality_gate/__init__.py +32 -0
- QuantNodes/core/quality_gate/complexity.py +94 -0
- QuantNodes/core/quality_gate/consistency.py +26 -0
- QuantNodes/core/quality_gate/node.py +97 -0
- QuantNodes/core/quality_gate/redundancy.py +51 -0
- QuantNodes/core/quality_gate/settings.py +43 -0
- QuantNodes/core/quality_gate/zoo.py +98 -0
- QuantNodes/core/serializable.py +116 -0
- QuantNodes/core/serialization.py +673 -0
- QuantNodes/core/tools.py +333 -0
- QuantNodes/core/trajectory/__init__.py +25 -0
- QuantNodes/core/trajectory/entry.py +116 -0
- QuantNodes/core/trajectory/lineage.py +67 -0
- QuantNodes/core/trajectory/pool.py +211 -0
- QuantNodes/core/trajectory/selector.py +140 -0
- QuantNodes/core/visualization/__init__.py +33 -0
- QuantNodes/core/visualization/builder.py +233 -0
- QuantNodes/core/visualization/gate_breakdown.py +140 -0
- QuantNodes/core/visualization/lineage_dag.py +203 -0
- QuantNodes/core/visualization/metric_distribution.py +125 -0
- QuantNodes/core/visualization/report.py +68 -0
- QuantNodes/database_node/__init__.py +69 -0
- QuantNodes/database_node/base.py +135 -0
- QuantNodes/database_node/clickhouse_node.py +272 -0
- QuantNodes/database_node/csv_node.py +83 -0
- QuantNodes/database_node/duckdb_node.py +86 -0
- QuantNodes/database_node/factory.py +83 -0
- QuantNodes/database_node/mysql_node.py +100 -0
- QuantNodes/database_node/parquet_node.py +75 -0
- QuantNodes/database_node/sqlite_node.py +67 -0
- QuantNodes/factor_node/__init__.py +50 -0
- QuantNodes/factor_node/factor.py +563 -0
- QuantNodes/factor_node/factor_db.py +421 -0
- QuantNodes/factor_node/factor_functions/__init__.py +252 -0
- QuantNodes/factor_node/factor_functions/_helpers.py +358 -0
- QuantNodes/factor_node/factor_functions/_helpers_debug.py +317 -0
- QuantNodes/factor_node/factor_functions/composite_ops.py +136 -0
- QuantNodes/factor_node/factor_functions/math_ops.py +433 -0
- QuantNodes/factor_node/factor_functions/section_ops.py +290 -0
- QuantNodes/factor_node/factor_functions/talib_ops.py +1293 -0
- QuantNodes/factor_node/factor_functions/time_ops.py +535 -0
- QuantNodes/factor_node/factor_operation.py +1115 -0
- QuantNodes/factor_node/factor_table.py +1073 -0
- QuantNodes/factor_node/quant_nodes_object.py +60 -0
- QuantNodes/mcp_server/__init__.py +27 -0
- QuantNodes/mcp_server/__main__.py +4 -0
- QuantNodes/mcp_server/server.py +272 -0
- QuantNodes/methods/__init__.py +28 -0
- QuantNodes/methods/pipeline.py +100 -0
- QuantNodes/methods/sandbox.py +102 -0
- QuantNodes/monitor/__init__.py +27 -0
- QuantNodes/monitor/agent_tools/__init__.py +5 -0
- QuantNodes/monitor/agent_tools/monitor_tool.py +98 -0
- QuantNodes/monitor/agent_tools/schedule_tool.py +98 -0
- QuantNodes/monitor/agent_tools/version_tool.py +133 -0
- QuantNodes/monitor/monitor/__init__.py +6 -0
- QuantNodes/monitor/monitor/alerter.py +60 -0
- QuantNodes/monitor/monitor/collector.py +164 -0
- QuantNodes/monitor/monitor/dashboard.py +115 -0
- QuantNodes/monitor/monitor/drift.py +190 -0
- QuantNodes/monitor/scheduler/__init__.py +4 -0
- QuantNodes/monitor/scheduler/runner.py +133 -0
- QuantNodes/monitor/scheduler/scheduler.py +184 -0
- QuantNodes/monitor/storage/__init__.py +16 -0
- QuantNodes/monitor/storage/models.py +70 -0
- QuantNodes/monitor/storage/repository.py +407 -0
- QuantNodes/monitor/version/__init__.py +4 -0
- QuantNodes/monitor/version/diff.py +81 -0
- QuantNodes/monitor/version/version_manager.py +182 -0
- QuantNodes/operator_node/__init__.py +28 -0
- QuantNodes/operator_node/base.py +97 -0
- QuantNodes/operator_node/query_node.py +129 -0
- QuantNodes/operator_node/sql_builder.py +125 -0
- QuantNodes/operator_node/sql_utils.py +172 -0
- QuantNodes/operator_node/transform.py +130 -0
- QuantNodes/operators/__init__.py +90 -0
- QuantNodes/operators/_engine.py +108 -0
- QuantNodes/operators/composite.py +161 -0
- QuantNodes/operators/composite_dag.py +667 -0
- QuantNodes/operators/composite_dag_ops.py +343 -0
- QuantNodes/operators/composite_dag_pandas_ops.py +382 -0
- QuantNodes/operators/custom.py +408 -0
- QuantNodes/operators/facade.py +164 -0
- QuantNodes/operators/math.py +163 -0
- QuantNodes/operators/proxy.py +29 -0
- QuantNodes/operators/registry.py +144 -0
- QuantNodes/operators/section.py +99 -0
- QuantNodes/operators/talib.py +757 -0
- QuantNodes/operators/templates.py +95 -0
- QuantNodes/operators/time_series.py +136 -0
- QuantNodes/prompts/__init__.py +20 -0
- QuantNodes/prompts/backtest/__init__.py +12 -0
- QuantNodes/prompts/backtest/factor_based.py +86 -0
- QuantNodes/prompts/backtest/standard.py +73 -0
- QuantNodes/prompts/factor/__init__.py +14 -0
- QuantNodes/prompts/factor/correlation.py +77 -0
- QuantNodes/prompts/factor/group_backtest.py +86 -0
- QuantNodes/prompts/factor/ic_analysis.py +91 -0
- QuantNodes/prompts/strategy/__init__.py +18 -0
- QuantNodes/prompts/strategy/market_neutral.py +96 -0
- QuantNodes/prompts/strategy/mean_reversion.py +107 -0
- QuantNodes/prompts/strategy/momentum.py +160 -0
- QuantNodes/prompts/strategy/pairs_trading.py +107 -0
- QuantNodes/prompts/strategy/trend_following.py +96 -0
- QuantNodes/research/README.md +106 -0
- QuantNodes/research/__init__.py +154 -0
- QuantNodes/research/_legacy_3c/__init__.py +61 -0
- QuantNodes/research/_legacy_3c/auto_researcher.py +289 -0
- QuantNodes/research/_legacy_3c/factor_evaluator.py +560 -0
- QuantNodes/research/_legacy_3c/factor_miner.py +318 -0
- QuantNodes/research/_legacy_3c/mcts_search.py +324 -0
- QuantNodes/research/factor_test/__init__.py +25 -0
- QuantNodes/research/factor_test/config.py +184 -0
- QuantNodes/research/factor_test/config_builder.py +276 -0
- QuantNodes/research/factor_test/e2e/data_prep.py +163 -0
- QuantNodes/research/factor_test/e2e/run_evolution_e2e.py +309 -0
- QuantNodes/research/factor_test/evolution_adapter.py +231 -0
- QuantNodes/research/factor_test/feedback_wrapper.py +102 -0
- QuantNodes/research/factor_test/ifind_db/__init__.py +7 -0
- QuantNodes/research/factor_test/ifind_db/fetcher.py +224 -0
- QuantNodes/research/factor_test/ifind_db/ifind_database.py +689 -0
- QuantNodes/research/factor_test/nodes/__init__.py +1 -0
- QuantNodes/research/factor_test/nodes/_base.py +91 -0
- QuantNodes/research/factor_test/nodes/adjust_date_node.py +48 -0
- QuantNodes/research/factor_test/nodes/configs.py +240 -0
- QuantNodes/research/factor_test/nodes/factor_neutralize_node.py +87 -0
- QuantNodes/research/factor_test/nodes/factor_preprocess_node.py +222 -0
- QuantNodes/research/factor_test/nodes/factor_score_node.py +141 -0
- QuantNodes/research/factor_test/nodes/factor_test_report_node.py +153 -0
- QuantNodes/research/factor_test/nodes/group_analyzer_node.py +317 -0
- QuantNodes/research/factor_test/nodes/ic_analyzer_node.py +112 -0
- QuantNodes/research/factor_test/nodes/load_data_node.py +100 -0
- QuantNodes/research/factor_test/nodes/long_short_node.py +93 -0
- QuantNodes/research/factor_test/nodes/neutralizers.py +222 -0
- QuantNodes/research/factor_test/nodes/preprocess_strategies.py +277 -0
- QuantNodes/research/factor_test/nodes/risk_correlation_node.py +112 -0
- QuantNodes/research/factor_test/nodes/sample_pool_filter_node.py +110 -0
- QuantNodes/research/factor_test/nodes/tradability_filter_node.py +92 -0
- QuantNodes/research/factor_test/pipeline_runner.py +305 -0
- QuantNodes/research/factor_test/pipeline_spec.py +216 -0
- QuantNodes/research/factor_test/utils/__init__.py +26 -0
- QuantNodes/research/factor_test/utils/constants.py +86 -0
- QuantNodes/research/factor_test/utils/data_loader.py +141 -0
- QuantNodes/research/factor_test/utils/date_utils.py +232 -0
- QuantNodes/research/factor_test/utils/file_loaders.py +150 -0
- QuantNodes/research/factor_test/utils/labels.py +37 -0
- QuantNodes/research/factor_test/utils/metrics_extractor.py +55 -0
- QuantNodes/research/factor_test/utils/performance_metrics.py +175 -0
- QuantNodes/research/factor_test/utils/safe_load.py +106 -0
- QuantNodes/research/quant_alpha/CHANGELOG.md +80 -0
- QuantNodes/research/quant_alpha/README.md +142 -0
- QuantNodes/research/quant_alpha/__init__.py +45 -0
- QuantNodes/research/quant_alpha/adapters/__init__.py +99 -0
- QuantNodes/research/quant_alpha/adapters/calculator.py +503 -0
- QuantNodes/research/quant_alpha/adapters/expression.py +387 -0
- QuantNodes/research/quant_alpha/alpha101_design/__init__.py +50 -0
- QuantNodes/research/quant_alpha/alpha101_design/few_shot_examples.py +243 -0
- QuantNodes/research/quant_alpha/alpha101_design/philosophy.py +474 -0
- QuantNodes/research/quant_alpha/alpha158_design/__init__.py +63 -0
- QuantNodes/research/quant_alpha/alpha158_design/few_shot_examples.py +219 -0
- QuantNodes/research/quant_alpha/alpha158_design/philosophy.py +240 -0
- QuantNodes/research/quant_alpha/evaluation/__init__.py +47 -0
- QuantNodes/research/quant_alpha/evaluation/baselines/__init__.py +8 -0
- QuantNodes/research/quant_alpha/evaluation/baselines/g1_handcrafted.py +135 -0
- QuantNodes/research/quant_alpha/evaluation/baselines/g2_llm_only.py +269 -0
- QuantNodes/research/quant_alpha/evaluation/baselines/g3_alpha_gpt.py +152 -0
- QuantNodes/research/quant_alpha/evaluation/clickhouse_data_loader.py +227 -0
- QuantNodes/research/quant_alpha/evaluation/contracts.py +376 -0
- QuantNodes/research/quant_alpha/evaluation/evaluators/__init__.py +6 -0
- QuantNodes/research/quant_alpha/evaluation/evaluators/polars_evaluator.py +545 -0
- QuantNodes/research/quant_alpha/evaluation/mock_data_loader.py +226 -0
- QuantNodes/research/quant_alpha/evaluation/runner.py +243 -0
- QuantNodes/research/quant_alpha/llm/__init__.py +38 -0
- QuantNodes/research/quant_alpha/llm/parser.py +681 -0
- QuantNodes/research/quant_alpha/logic_driven_pipeline.py +411 -0
- QuantNodes/research/quant_alpha/logic_mining/__init__.py +74 -0
- QuantNodes/research/quant_alpha/logic_mining/compiler.py +457 -0
- QuantNodes/research/quant_alpha/logic_mining/generator.py +366 -0
- QuantNodes/research/quant_alpha/logic_mining/models.py +252 -0
- QuantNodes/research/quant_alpha/logic_mining/parser.py +287 -0
- QuantNodes/research/quant_alpha/logic_mining/pipelines.py +297 -0
- QuantNodes/research/quant_alpha/logic_mining/sources.py +149 -0
- QuantNodes/research/quant_alpha/mcts/__init__.py +66 -0
- QuantNodes/research/quant_alpha/mcts/cache.py +262 -0
- QuantNodes/research/quant_alpha/mcts/extension_ops.py +320 -0
- QuantNodes/research/quant_alpha/mcts/feedback.py +825 -0
- QuantNodes/research/quant_alpha/mcts/op_prior.py +180 -0
- QuantNodes/research/quant_alpha/mcts/search.py +540 -0
- QuantNodes/research/quant_alpha/mcts/tree.py +201 -0
- QuantNodes/research/quant_alpha/operator_vocab/__init__.py +50 -0
- QuantNodes/research/quant_alpha/operator_vocab/config.py +54 -0
- QuantNodes/research/quant_alpha/operator_vocab/metadata.py +263 -0
- QuantNodes/research/quant_alpha/operator_vocab/vocabulary.py +481 -0
- QuantNodes/research/quant_alpha/pipeline.py +1027 -0
- QuantNodes/research/quant_alpha/types/__init__.py +27 -0
- QuantNodes/research/quant_alpha/types/constants.py +28 -0
- QuantNodes/research/quant_alpha/types/state.py +205 -0
- QuantNodes/research/quant_alpha/workflow/__init__.py +32 -0
- QuantNodes/research/quant_alpha/workflow/alpha_gpt.py +911 -0
- QuantNodes/research/quant_alpha/workflow/alpha_logics.py +416 -0
- QuantNodes/research/quant_alpha/workflow/state.py +27 -0
- QuantNodes/research/report_reproducer.py +485 -0
- QuantNodes/research/wiki.py +1155 -0
- QuantNodes/symbolic/__init__.py +51 -0
- QuantNodes/symbolic/compiler.py +113 -0
- QuantNodes/symbolic/dialect.py +260 -0
- QuantNodes/symbolic/executor.py +147 -0
- QuantNodes/symbolic/expression.py +234 -0
- QuantNodes/symbolic/functions.py +433 -0
- QuantNodes/symbolic/optimizer.py +165 -0
- QuantNodes/ui_node/__init__.py +30 -0
- QuantNodes/ui_node/base.py +222 -0
- quantnodes-3.0.0.dist-info/METADATA +463 -0
- quantnodes-3.0.0.dist-info/RECORD +399 -0
- quantnodes-3.0.0.dist-info/WHEEL +5 -0
- quantnodes-3.0.0.dist-info/entry_points.txt +24 -0
- quantnodes-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
operator_lookup.py - 算子查询工具
|
|
4
|
+
|
|
5
|
+
让 agent 动态发现 OperatorVocab 中的 162 个可用算子,
|
|
6
|
+
获取算子签名、参数、示例,以及校验公式有效性。
|
|
7
|
+
|
|
8
|
+
用法 (agent 调用)::
|
|
9
|
+
|
|
10
|
+
# 列出所有算子
|
|
11
|
+
operator_lookup(action="list_operators")
|
|
12
|
+
operator_lookup(action="list_operators", category="time")
|
|
13
|
+
|
|
14
|
+
# 获取算子详情
|
|
15
|
+
operator_lookup(action="get_operator_info", name="ts_mean")
|
|
16
|
+
|
|
17
|
+
# 校验公式
|
|
18
|
+
operator_lookup(action="validate_formula", formula="rank(ts_mean(close, 20))")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
from typing import Any, Dict, Optional, Sequence
|
|
25
|
+
|
|
26
|
+
from .base import Tool
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
__all__ = ["OperatorLookupTool"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class OperatorLookupTool(Tool):
|
|
34
|
+
"""算子查询工具 — 让 agent 发现可用算子
|
|
35
|
+
|
|
36
|
+
3 个 action:
|
|
37
|
+
- list_operators: 列出所有算子(可按类别过滤)
|
|
38
|
+
- get_operator_info: 获取单个算子详情(签名、参数、示例)
|
|
39
|
+
- validate_formula: 校验公式是否有效
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def name(self) -> str:
|
|
47
|
+
return "operator_lookup"
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def description(self) -> str:
|
|
51
|
+
return (
|
|
52
|
+
"查询 QuantNodes OperatorVocab 中的可用算子(162 个)。"
|
|
53
|
+
"用于发现算子、获取用法说明、校验公式有效性。"
|
|
54
|
+
"生成 alpha 因子公式前,应先调用此工具获取可用算子列表。"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def parameters(self) -> Dict[str, Any]:
|
|
59
|
+
return {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"properties": {
|
|
62
|
+
"action": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"enum": [
|
|
65
|
+
"list_operators",
|
|
66
|
+
"get_operator_info",
|
|
67
|
+
"validate_formula",
|
|
68
|
+
],
|
|
69
|
+
"description": (
|
|
70
|
+
"list_operators: 列出所有算子(可按 category 过滤)。"
|
|
71
|
+
"get_operator_info: 获取单个算子详情。"
|
|
72
|
+
"validate_formula: 校验公式是否有效。"
|
|
73
|
+
),
|
|
74
|
+
},
|
|
75
|
+
"category": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"enum": ["time", "point", "section", "multi_section"],
|
|
78
|
+
"description": (
|
|
79
|
+
"算子类别过滤(list_operators 时使用)。"
|
|
80
|
+
"time=时间序列, point=逐点, section=截面, multi_section=多截面"
|
|
81
|
+
),
|
|
82
|
+
},
|
|
83
|
+
"name": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"description": "算子名(get_operator_info 时使用)",
|
|
86
|
+
},
|
|
87
|
+
"formula": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"description": "公式字符串(validate_formula 时使用)",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
"required": ["action"],
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def read_only(self) -> bool:
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
async def execute(
|
|
100
|
+
self,
|
|
101
|
+
action: str,
|
|
102
|
+
category: Optional[str] = None,
|
|
103
|
+
name: Optional[str] = None,
|
|
104
|
+
formula: Optional[str] = None,
|
|
105
|
+
**kwargs: Any,
|
|
106
|
+
) -> Dict[str, Any]:
|
|
107
|
+
"""执行算子查询"""
|
|
108
|
+
try:
|
|
109
|
+
if action == "list_operators":
|
|
110
|
+
return self._list_operators(category)
|
|
111
|
+
elif action == "get_operator_info":
|
|
112
|
+
if not name:
|
|
113
|
+
return {"error": "name 参数必填"}
|
|
114
|
+
return self._get_operator_info(name)
|
|
115
|
+
elif action == "validate_formula":
|
|
116
|
+
if not formula:
|
|
117
|
+
return {"error": "formula 参数必填"}
|
|
118
|
+
return self._validate_formula(formula)
|
|
119
|
+
else:
|
|
120
|
+
return {"error": f"未知 action: {action}"}
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.error("OperatorLookup failed: %s", e)
|
|
123
|
+
return {"error": str(e)}
|
|
124
|
+
|
|
125
|
+
def _list_operators(self, category: Optional[str] = None) -> Dict[str, Any]:
|
|
126
|
+
"""列出所有算子(可按类别过滤)"""
|
|
127
|
+
from QuantNodes.research.quant_alpha.operator_vocab import (
|
|
128
|
+
list_vocab_operators,
|
|
129
|
+
get_vocab_metadata,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
ops = list_vocab_operators(category=category)
|
|
133
|
+
result = []
|
|
134
|
+
for op_name in ops:
|
|
135
|
+
meta = get_vocab_metadata(op_name)
|
|
136
|
+
if meta:
|
|
137
|
+
result.append({
|
|
138
|
+
"name": op_name,
|
|
139
|
+
"category": meta.category,
|
|
140
|
+
"signature": meta.signature,
|
|
141
|
+
"doc": meta.doc,
|
|
142
|
+
})
|
|
143
|
+
else:
|
|
144
|
+
result.append({"name": op_name})
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
"operators": result,
|
|
148
|
+
"total": len(result),
|
|
149
|
+
"category_filter": category,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
def _get_operator_info(self, name: str) -> Dict[str, Any]:
|
|
153
|
+
"""获取单个算子详情"""
|
|
154
|
+
from QuantNodes.research.quant_alpha.operator_vocab import get_vocab_metadata
|
|
155
|
+
|
|
156
|
+
meta = get_vocab_metadata(name)
|
|
157
|
+
if meta is None:
|
|
158
|
+
return {"error": f"算子 '{name}' 不存在"}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
"name": meta.name,
|
|
162
|
+
"category": meta.category,
|
|
163
|
+
"category_tags": meta.category_tags,
|
|
164
|
+
"signature": meta.signature,
|
|
165
|
+
"parameters": meta.parameters,
|
|
166
|
+
"doc": meta.doc,
|
|
167
|
+
"default_window": meta.default_window,
|
|
168
|
+
"examples": meta.examples,
|
|
169
|
+
"difficulty": meta.difficulty,
|
|
170
|
+
"output_dtype": meta.output_dtype,
|
|
171
|
+
"requires_group_by": meta.requires_group_by,
|
|
172
|
+
"composes_with": meta.composes_with,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
def _validate_formula(self, formula: str) -> Dict[str, Any]:
|
|
176
|
+
"""校验公式是否有效"""
|
|
177
|
+
from QuantNodes.research.quant_alpha.operator_vocab import OperatorVocab
|
|
178
|
+
import polars as pl
|
|
179
|
+
|
|
180
|
+
# 构造最小测试数据
|
|
181
|
+
test_data = pl.DataFrame({
|
|
182
|
+
"date": ["2020-01-01", "2020-01-02", "2020-01-03"] * 3,
|
|
183
|
+
"code": ["A"] * 3 + ["B"] * 3 + ["C"] * 3,
|
|
184
|
+
"open": [10.0] * 9,
|
|
185
|
+
"high": [10.5] * 9,
|
|
186
|
+
"low": [9.5] * 9,
|
|
187
|
+
"close": [10.0, 10.1, 10.2, 11.0, 11.1, 11.2, 12.0, 12.1, 12.2],
|
|
188
|
+
"vol": [1000.0] * 9,
|
|
189
|
+
"amount": [10000.0] * 9,
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
vocab = OperatorVocab.default()
|
|
194
|
+
result = vocab.evaluate(
|
|
195
|
+
formula=formula,
|
|
196
|
+
data=test_data,
|
|
197
|
+
date_column="date",
|
|
198
|
+
code_column="code",
|
|
199
|
+
)
|
|
200
|
+
if result is not None and len(result) == len(test_data):
|
|
201
|
+
return {
|
|
202
|
+
"formula": formula,
|
|
203
|
+
"valid": True,
|
|
204
|
+
"error": None,
|
|
205
|
+
"result_length": len(result),
|
|
206
|
+
}
|
|
207
|
+
else:
|
|
208
|
+
return {
|
|
209
|
+
"formula": formula,
|
|
210
|
+
"valid": False,
|
|
211
|
+
"error": "评估结果为空或长度不匹配",
|
|
212
|
+
}
|
|
213
|
+
except Exception as e:
|
|
214
|
+
return {
|
|
215
|
+
"formula": formula,
|
|
216
|
+
"valid": False,
|
|
217
|
+
"error": str(e),
|
|
218
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
输出截断模块
|
|
4
|
+
|
|
5
|
+
提供输出长度控制功能。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class TruncatedOutput:
|
|
13
|
+
"""截断后的输出"""
|
|
14
|
+
content: str
|
|
15
|
+
truncated: bool
|
|
16
|
+
total_lines: int
|
|
17
|
+
total_bytes: int
|
|
18
|
+
kept_lines: int
|
|
19
|
+
kept_bytes: int
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def truncate_output(
|
|
23
|
+
output: str,
|
|
24
|
+
max_lines: int = 10000,
|
|
25
|
+
max_bytes: int = 1024 * 1024,
|
|
26
|
+
) -> TruncatedOutput:
|
|
27
|
+
"""截断输出
|
|
28
|
+
|
|
29
|
+
策略:
|
|
30
|
+
1. 保留前 max_lines/2 行
|
|
31
|
+
2. 保留后 max_lines/2 行
|
|
32
|
+
3. 中间用省略标记替代
|
|
33
|
+
4. 同时检查字节限制
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
output: 原始输出
|
|
37
|
+
max_lines: 最大行数
|
|
38
|
+
max_bytes: 最大字节数
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
TruncatedOutput 对象
|
|
42
|
+
"""
|
|
43
|
+
lines = output.split("\n")
|
|
44
|
+
total_lines = len(lines)
|
|
45
|
+
total_bytes = len(output.encode("utf-8"))
|
|
46
|
+
|
|
47
|
+
truncated = False
|
|
48
|
+
kept_lines = total_lines
|
|
49
|
+
kept_bytes = total_bytes
|
|
50
|
+
|
|
51
|
+
if total_lines > max_lines or total_bytes > max_bytes:
|
|
52
|
+
truncated = True
|
|
53
|
+
half = max_lines // 2
|
|
54
|
+
kept_lines = min(total_lines, max_lines)
|
|
55
|
+
|
|
56
|
+
if total_lines > max_lines:
|
|
57
|
+
kept_lines = max_lines
|
|
58
|
+
head = lines[:half]
|
|
59
|
+
tail = lines[-(max_lines - half):]
|
|
60
|
+
lines = head + ["... (truncated) ..."] + tail
|
|
61
|
+
|
|
62
|
+
result = "\n".join(lines)
|
|
63
|
+
|
|
64
|
+
if len(result.encode("utf-8")) > max_bytes:
|
|
65
|
+
result = result[:max_bytes] + "\n... (truncated by bytes) ..."
|
|
66
|
+
kept_bytes = max_bytes
|
|
67
|
+
else:
|
|
68
|
+
result = output
|
|
69
|
+
|
|
70
|
+
return TruncatedOutput(
|
|
71
|
+
content=result,
|
|
72
|
+
truncated=truncated,
|
|
73
|
+
total_lines=total_lines,
|
|
74
|
+
total_bytes=total_bytes,
|
|
75
|
+
kept_lines=kept_lines,
|
|
76
|
+
kept_bytes=kept_bytes,
|
|
77
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
路径检查模块
|
|
4
|
+
|
|
5
|
+
提供项目边界检查功能。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_within_project(filepath: str, project_root: str) -> bool:
|
|
12
|
+
"""检查文件路径是否在项目目录内
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
filepath: 文件路径
|
|
16
|
+
project_root: 项目根目录
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
True if within project, False otherwise
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
file_path = Path(filepath).resolve()
|
|
23
|
+
project_path = Path(project_root).resolve()
|
|
24
|
+
return file_path.is_relative_to(project_path)
|
|
25
|
+
except (ValueError, OSError):
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def assert_within_project(filepath: str, project_root: str) -> None:
|
|
30
|
+
"""断言文件路径在项目目录内
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ExternalDirectoryError: 如果文件在项目外
|
|
34
|
+
"""
|
|
35
|
+
if not is_within_project(filepath, project_root):
|
|
36
|
+
raise ExternalDirectoryError(
|
|
37
|
+
f"Access to external directory denied: {filepath}"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ExternalDirectoryError(Exception):
|
|
42
|
+
"""外部目录访问错误"""
|
|
43
|
+
pass
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
Pipeline工具 - Agent 工具版本
|
|
4
|
+
|
|
5
|
+
验证 QuantNodes Pipeline 代码的正确性。
|
|
6
|
+
|
|
7
|
+
实现: 委托给 QuantNodes.methods.pipeline.validate_pipeline,
|
|
8
|
+
保持 Tool 接口 (async, dict 返回) 以兼容 Agent 工具注册。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Any, Dict
|
|
12
|
+
|
|
13
|
+
from QuantNodes.agent.tools.base import Tool
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PipelineTool(Tool):
|
|
17
|
+
"""Pipeline验证工具
|
|
18
|
+
|
|
19
|
+
验证 QuantNodes Pipeline 代码的正确性,包括:
|
|
20
|
+
- 语法检查
|
|
21
|
+
- 安全检查(CodeSandbox)
|
|
22
|
+
- 节点提取
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def name(self) -> str:
|
|
30
|
+
return "pipeline"
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def description(self) -> str:
|
|
34
|
+
return "验证 QuantNodes Pipeline 代码的正确性,检查语法和结构"
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def parameters(self) -> Dict[str, Any]:
|
|
38
|
+
return {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"properties": {
|
|
41
|
+
"code": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "QuantNodes Pipeline Python代码"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"required": ["code"]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def read_only(self) -> bool:
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
async def execute(self, code: str, **kwargs) -> Dict[str, Any]:
|
|
54
|
+
from QuantNodes.methods.pipeline import validate_pipeline
|
|
55
|
+
result = validate_pipeline(code=code)
|
|
56
|
+
return {
|
|
57
|
+
"is_valid": result.is_valid,
|
|
58
|
+
"errors": result.errors,
|
|
59
|
+
"warnings": result.warnings,
|
|
60
|
+
"nodes": result.nodes,
|
|
61
|
+
"security_status": result.security_status,
|
|
62
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
工具注册表
|
|
4
|
+
|
|
5
|
+
管理工具的注册、查询与执行
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, List
|
|
9
|
+
import asyncio
|
|
10
|
+
|
|
11
|
+
from .base import Tool, ToolExecutionResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ToolRegistry:
|
|
15
|
+
"""工具注册表与执行入口"""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self._tools: Dict[str, Tool] = {}
|
|
19
|
+
self._cached_schemas: List[Dict[str, Any]] | None = None
|
|
20
|
+
|
|
21
|
+
def register(self, tool: Tool) -> None:
|
|
22
|
+
"""注册工具"""
|
|
23
|
+
self._tools[tool.name] = tool
|
|
24
|
+
self._cached_schemas = None
|
|
25
|
+
|
|
26
|
+
def unregister(self, name: str) -> None:
|
|
27
|
+
"""注销工具"""
|
|
28
|
+
if name in self._tools:
|
|
29
|
+
del self._tools[name]
|
|
30
|
+
self._cached_schemas = None
|
|
31
|
+
|
|
32
|
+
def get(self, name: str) -> Tool | None:
|
|
33
|
+
"""获取工具"""
|
|
34
|
+
return self._tools.get(name)
|
|
35
|
+
|
|
36
|
+
def list_tools(self) -> List[Tool]:
|
|
37
|
+
"""列出所有已注册工具"""
|
|
38
|
+
return list(self._tools.values())
|
|
39
|
+
|
|
40
|
+
def get_tool_schemas(self) -> List[Dict[str, Any]]:
|
|
41
|
+
"""Return OpenAI function-calling schemas for all tools (cached).
|
|
42
|
+
|
|
43
|
+
v3.0.0 contract: if a tool cannot produce a schema (e.g. the
|
|
44
|
+
local ``Tool`` ABC when ``nanobot-ai`` is not installed), we
|
|
45
|
+
fall back to a minimal ``{"type": "function", "function": {"name": ..., "description": ..., "parameters": {"type": "object", "properties": {}}}}``
|
|
46
|
+
shape so the registry's schema list is always usable.
|
|
47
|
+
"""
|
|
48
|
+
if self._cached_schemas is None:
|
|
49
|
+
schemas: List[Dict[str, Any]] = []
|
|
50
|
+
for tool in self._tools.values():
|
|
51
|
+
try:
|
|
52
|
+
schemas.append(tool.to_openai_schema())
|
|
53
|
+
except Exception:
|
|
54
|
+
# Fallback: minimal schema from raw fields
|
|
55
|
+
schemas.append({
|
|
56
|
+
"type": "function",
|
|
57
|
+
"function": {
|
|
58
|
+
"name": tool.name,
|
|
59
|
+
"description": tool.description,
|
|
60
|
+
"parameters": tool.parameters,
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
self._cached_schemas = schemas
|
|
64
|
+
return self._cached_schemas
|
|
65
|
+
|
|
66
|
+
async def execute_tool(self, name: str, **kwargs: Any) -> ToolExecutionResult:
|
|
67
|
+
"""Execute a single tool by name.
|
|
68
|
+
|
|
69
|
+
v3.0.0 contract: we attempt to call ``tool.cast_params(kwargs)`` and
|
|
70
|
+
``tool.validate_params(params)`` for forward-compat with the
|
|
71
|
+
upstream nanobot Tool API. If those methods don't exist on the
|
|
72
|
+
tool (e.g. local ``Tool`` ABC when ``nanobot-ai`` is not
|
|
73
|
+
installed, or third-party tools that don't implement them), we
|
|
74
|
+
gracefully fall back to passing the raw ``kwargs`` to
|
|
75
|
+
``tool.execute()``.
|
|
76
|
+
"""
|
|
77
|
+
tool = self._tools.get(name)
|
|
78
|
+
if not tool:
|
|
79
|
+
return ToolExecutionResult(
|
|
80
|
+
tool_name=name,
|
|
81
|
+
success=False,
|
|
82
|
+
content=None,
|
|
83
|
+
error=f"Tool '{name}' not found"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
# Optional pre-processing hooks. Missing methods are OK —
|
|
88
|
+
# the bare v3.0.0 ``Tool`` ABC doesn't define them.
|
|
89
|
+
params: Dict[str, Any] = kwargs
|
|
90
|
+
cast = getattr(tool, "cast_params", None)
|
|
91
|
+
if callable(cast):
|
|
92
|
+
params = cast(kwargs)
|
|
93
|
+
validate = getattr(tool, "validate_params", None)
|
|
94
|
+
if callable(validate):
|
|
95
|
+
errors = validate(params)
|
|
96
|
+
if errors:
|
|
97
|
+
return ToolExecutionResult(
|
|
98
|
+
tool_name=name,
|
|
99
|
+
success=False,
|
|
100
|
+
content=None,
|
|
101
|
+
error=f"Parameter validation failed: {', '.join(errors)}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
result = await tool.execute(**params)
|
|
105
|
+
return ToolExecutionResult(
|
|
106
|
+
tool_name=name,
|
|
107
|
+
success=True,
|
|
108
|
+
content=result
|
|
109
|
+
)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
return ToolExecutionResult(
|
|
112
|
+
tool_name=name,
|
|
113
|
+
success=False,
|
|
114
|
+
content=None,
|
|
115
|
+
error=str(e)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
async def execute_tools_parallel(
|
|
119
|
+
self,
|
|
120
|
+
tool_calls: List[Dict[str, Any]],
|
|
121
|
+
) -> List[ToolExecutionResult]:
|
|
122
|
+
"""并发执行多个工具(只读工具并发,有副作用工具串行)"""
|
|
123
|
+
results: List[ToolExecutionResult] = []
|
|
124
|
+
|
|
125
|
+
# 分组:只读工具并发执行,其他串行
|
|
126
|
+
read_only_calls: List[Dict[str, Any]] = []
|
|
127
|
+
write_calls: List[Dict[str, Any]] = []
|
|
128
|
+
|
|
129
|
+
for call in tool_calls:
|
|
130
|
+
tool = self._tools.get(call.get("name", ""))
|
|
131
|
+
if tool and tool.read_only:
|
|
132
|
+
read_only_calls.append(call)
|
|
133
|
+
else:
|
|
134
|
+
write_calls.append(call)
|
|
135
|
+
|
|
136
|
+
# 并发执行只读工具
|
|
137
|
+
if read_only_calls:
|
|
138
|
+
tasks = [
|
|
139
|
+
self.execute_tool(call["name"], **call.get("arguments", {}))
|
|
140
|
+
for call in read_only_calls
|
|
141
|
+
]
|
|
142
|
+
results.extend(await asyncio.gather(*tasks))
|
|
143
|
+
|
|
144
|
+
# 串行执行有副作用工具
|
|
145
|
+
for call in write_calls:
|
|
146
|
+
name = call.get("name", "")
|
|
147
|
+
result = await self.execute_tool(name, **call.get("arguments", {}))
|
|
148
|
+
results.append(result)
|
|
149
|
+
|
|
150
|
+
return results
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
沙箱工具 - Agent 工具版本
|
|
4
|
+
|
|
5
|
+
封装 QuantNodes AI 模块的 CodeSandbox,用于代码安全验证。
|
|
6
|
+
|
|
7
|
+
实现: 委托给 self._sandbox.validate() (CodeSandbox 实例)。
|
|
8
|
+
原本重复的 methods/sandbox.validate_code 现已删除,
|
|
9
|
+
此模块通过 _sandbox 属性复用 CodeSandbox,避免逻辑重复。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any, Dict
|
|
13
|
+
|
|
14
|
+
from QuantNodes.ai.sandbox import CodeSandbox as QNCodeSandbox
|
|
15
|
+
from QuantNodes.agent.tools.base import Tool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SandboxTool(Tool):
|
|
19
|
+
"""代码安全沙箱工具
|
|
20
|
+
|
|
21
|
+
提供代码安全校验,防止执行危险操作。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, allow_warnings: bool = False, max_code_length: int = 10000):
|
|
25
|
+
self._sandbox = QNCodeSandbox(
|
|
26
|
+
allow_warnings=allow_warnings,
|
|
27
|
+
max_code_length=max_code_length
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def name(self) -> str:
|
|
32
|
+
return "sandbox"
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def description(self) -> str:
|
|
36
|
+
return "验证Python代码的安全性,检查危险操作。不执行代码,只返回验证结果"
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def parameters(self) -> Dict[str, Any]:
|
|
40
|
+
return {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"code": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "待验证的Python代码"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"required": ["code"]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def read_only(self) -> bool:
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
async def execute(self, code: str, **kwargs) -> Dict[str, Any]:
|
|
56
|
+
result = self._sandbox.validate(code)
|
|
57
|
+
return {
|
|
58
|
+
"is_safe": result.is_safe,
|
|
59
|
+
"errors": result.errors,
|
|
60
|
+
"warnings": result.warnings,
|
|
61
|
+
"warnings_only": result.warnings_only,
|
|
62
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
Shell 安全模块
|
|
4
|
+
|
|
5
|
+
提供命令超时、执行控制等功能。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ShellConfig:
|
|
15
|
+
"""Shell 执行配置"""
|
|
16
|
+
timeout_seconds: int = 120
|
|
17
|
+
max_output_bytes: int = 1024 * 1024
|
|
18
|
+
max_output_lines: int = 10000
|
|
19
|
+
project_root: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def execute_with_timeout(
|
|
23
|
+
command: str,
|
|
24
|
+
cwd: Optional[str] = None,
|
|
25
|
+
timeout: int = 120,
|
|
26
|
+
env: Optional[dict] = None,
|
|
27
|
+
) -> tuple[int, str, str]:
|
|
28
|
+
"""执行 shell 命令(带超时)
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
command: 要执行的命令
|
|
32
|
+
cwd: 工作目录
|
|
33
|
+
timeout: 超时秒数
|
|
34
|
+
env: 环境变量
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
(exit_code, stdout, stderr)
|
|
38
|
+
"""
|
|
39
|
+
proc = await asyncio.create_subprocess_shell(
|
|
40
|
+
command,
|
|
41
|
+
stdout=asyncio.subprocess.PIPE,
|
|
42
|
+
stderr=asyncio.subprocess.PIPE,
|
|
43
|
+
cwd=cwd,
|
|
44
|
+
env=env,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
stdout, stderr = await asyncio.wait_for(
|
|
49
|
+
proc.communicate(),
|
|
50
|
+
timeout=timeout,
|
|
51
|
+
)
|
|
52
|
+
return proc.returncode or 0, stdout.decode(), stderr.decode()
|
|
53
|
+
except asyncio.TimeoutError:
|
|
54
|
+
proc.kill()
|
|
55
|
+
await proc.wait()
|
|
56
|
+
raise ShellTimeoutError(
|
|
57
|
+
f"Command timed out after {timeout}s: {command[:100]}"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ShellTimeoutError(Exception):
|
|
62
|
+
"""Shell 命令超时"""
|
|
63
|
+
pass
|