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,326 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
速率限制模块 (Rate Limiter)
|
|
4
|
+
|
|
5
|
+
提供 Token Bucket 算法实现,用于控制 LLM API 请求频率。
|
|
6
|
+
解决 OpenRouter 免费账号的严格速率限制问题(每天 50 次请求)。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
import asyncio
|
|
11
|
+
import threading
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TokenBucket:
|
|
15
|
+
"""同步令牌桶速率限制器
|
|
16
|
+
|
|
17
|
+
用于多线程环境下的请求频率控制。
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
requests_per_second: 每秒允许的请求数(免费账号建议 0.5)
|
|
21
|
+
burst: 突发容量,允许的最大突发请求数
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, requests_per_second: float = 0.5, burst: int = 1):
|
|
25
|
+
"""
|
|
26
|
+
Args:
|
|
27
|
+
requests_per_second: 每秒允许的请求数,默认 0.5 即每2秒1次请求
|
|
28
|
+
burst: 突发容量,默认 1 表示每次最多处理1个请求
|
|
29
|
+
"""
|
|
30
|
+
if requests_per_second <= 0:
|
|
31
|
+
raise ValueError("requests_per_second must be positive")
|
|
32
|
+
if burst < 1:
|
|
33
|
+
raise ValueError("burst must be at least 1")
|
|
34
|
+
|
|
35
|
+
self.rate = requests_per_second
|
|
36
|
+
self.burst = burst
|
|
37
|
+
self.tokens = float(burst)
|
|
38
|
+
self.last_update = time.time()
|
|
39
|
+
self.lock = threading.Lock()
|
|
40
|
+
|
|
41
|
+
def _refill(self) -> None:
|
|
42
|
+
"""补充令牌"""
|
|
43
|
+
now = time.time()
|
|
44
|
+
elapsed = now - self.last_update
|
|
45
|
+
self.tokens = min(self.burst, self.tokens + elapsed * self.rate)
|
|
46
|
+
self.last_update = now
|
|
47
|
+
|
|
48
|
+
def acquire(self, blocking: bool = True) -> bool:
|
|
49
|
+
"""获取令牌
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
blocking: 是否阻塞等待令牌可用
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
True if token acquired, False if not (non-blocking mode only)
|
|
56
|
+
"""
|
|
57
|
+
with self.lock:
|
|
58
|
+
self._refill()
|
|
59
|
+
|
|
60
|
+
if self.tokens >= 1:
|
|
61
|
+
self.tokens -= 1
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
if not blocking:
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
wait_time = (1 - self.tokens) / self.rate
|
|
68
|
+
time.sleep(wait_time)
|
|
69
|
+
self.tokens = 0
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
def try_acquire(self) -> bool:
|
|
73
|
+
"""非阻塞尝试获取令牌"""
|
|
74
|
+
return self.acquire(blocking=False)
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def available_tokens(self) -> float:
|
|
78
|
+
"""当前可用令牌数"""
|
|
79
|
+
with self.lock:
|
|
80
|
+
self._refill()
|
|
81
|
+
return self.tokens
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class AsyncTokenBucket:
|
|
85
|
+
"""异步令牌桶速率限制器
|
|
86
|
+
|
|
87
|
+
用于 asyncio 环境下的请求频率控制。
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
requests_per_second: 每秒允许的请求数(免费账号建议 0.5)
|
|
91
|
+
burst: 突发容量
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(self, requests_per_second: float = 0.5, burst: int = 1):
|
|
95
|
+
"""
|
|
96
|
+
Args:
|
|
97
|
+
requests_per_second: 每秒允许的请求数,默认 0.5 即每2秒1次请求
|
|
98
|
+
burst: 突发容量,默认 1
|
|
99
|
+
"""
|
|
100
|
+
if requests_per_second <= 0:
|
|
101
|
+
raise ValueError("requests_per_second must be positive")
|
|
102
|
+
if burst < 1:
|
|
103
|
+
raise ValueError("burst must be at least 1")
|
|
104
|
+
|
|
105
|
+
self.rate = requests_per_second
|
|
106
|
+
self.burst = burst
|
|
107
|
+
self.tokens = float(burst)
|
|
108
|
+
self.last_update = time.time()
|
|
109
|
+
self.lock = asyncio.Lock()
|
|
110
|
+
|
|
111
|
+
async def _refill(self) -> None:
|
|
112
|
+
"""补充令牌"""
|
|
113
|
+
now = time.time()
|
|
114
|
+
elapsed = now - self.last_update
|
|
115
|
+
self.tokens = min(self.burst, self.tokens + elapsed * self.rate)
|
|
116
|
+
self.last_update = now
|
|
117
|
+
|
|
118
|
+
async def acquire(self) -> None:
|
|
119
|
+
"""获取令牌,必要时等待
|
|
120
|
+
|
|
121
|
+
这是阻塞方法,会等待直到令牌可用。
|
|
122
|
+
"""
|
|
123
|
+
async with self.lock:
|
|
124
|
+
await self._refill()
|
|
125
|
+
|
|
126
|
+
if self.tokens >= 1:
|
|
127
|
+
self.tokens -= 1
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
wait_time = (1 - self.tokens) / self.rate
|
|
131
|
+
await asyncio.sleep(wait_time)
|
|
132
|
+
self.tokens = 0
|
|
133
|
+
|
|
134
|
+
async def try_acquire(self) -> bool:
|
|
135
|
+
"""非阻塞尝试获取令牌
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
True if token acquired, False otherwise
|
|
139
|
+
"""
|
|
140
|
+
async with self.lock:
|
|
141
|
+
await self._refill()
|
|
142
|
+
|
|
143
|
+
if self.tokens >= 1:
|
|
144
|
+
self.tokens -= 1
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
async def wait_time(self) -> float:
|
|
150
|
+
"""计算获取令牌需要等待的时间
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
等待时间(秒),如果立即可用则返回 0
|
|
154
|
+
"""
|
|
155
|
+
async with self.lock:
|
|
156
|
+
await self._refill()
|
|
157
|
+
|
|
158
|
+
if self.tokens >= 1:
|
|
159
|
+
return 0.0
|
|
160
|
+
|
|
161
|
+
return (1 - self.tokens) / self.rate
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def available_tokens(self) -> float:
|
|
165
|
+
"""当前可用令牌数(非线程安全,仅供调试)"""
|
|
166
|
+
return self.tokens
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class SlidingWindowRateLimiter:
|
|
170
|
+
"""滑动窗口速率限制器
|
|
171
|
+
|
|
172
|
+
另一种速率限制实现,在固定时间窗口内限制请求数。
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
max_requests: 时间窗口内最大请求数
|
|
176
|
+
window_seconds: 时间窗口长度(秒)
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(self, max_requests: int = 10, window_seconds: float = 60.0):
|
|
180
|
+
if max_requests <= 0:
|
|
181
|
+
raise ValueError("max_requests must be positive")
|
|
182
|
+
if window_seconds <= 0:
|
|
183
|
+
raise ValueError("window_seconds must be positive")
|
|
184
|
+
|
|
185
|
+
self.max_requests = max_requests
|
|
186
|
+
self.window_seconds = window_seconds
|
|
187
|
+
self.requests: list[float] = []
|
|
188
|
+
self.lock = threading.Lock()
|
|
189
|
+
|
|
190
|
+
def _clean_old_requests(self) -> None:
|
|
191
|
+
"""清除过期请求记录"""
|
|
192
|
+
now = time.time()
|
|
193
|
+
cutoff = now - self.window_seconds
|
|
194
|
+
self.requests = [t for t in self.requests if t > cutoff]
|
|
195
|
+
|
|
196
|
+
def acquire(self, blocking: bool = True) -> bool:
|
|
197
|
+
"""获取许可"""
|
|
198
|
+
with self.lock:
|
|
199
|
+
self._clean_old_requests()
|
|
200
|
+
|
|
201
|
+
if len(self.requests) < self.max_requests:
|
|
202
|
+
self.requests.append(time.time())
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
if not blocking:
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
oldest = self.requests[0]
|
|
209
|
+
wait_time = oldest + self.window_seconds - time.time()
|
|
210
|
+
if wait_time > 0:
|
|
211
|
+
time.sleep(wait_time)
|
|
212
|
+
|
|
213
|
+
self._clean_old_requests()
|
|
214
|
+
self.requests.append(time.time())
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
def try_acquire(self) -> bool:
|
|
218
|
+
"""非阻塞尝试获取许可"""
|
|
219
|
+
return self.acquire(blocking=False)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class AsyncSlidingWindowRateLimiter:
|
|
223
|
+
"""异步滑动窗口速率限制器"""
|
|
224
|
+
|
|
225
|
+
def __init__(self, max_requests: int = 10, window_seconds: float = 60.0):
|
|
226
|
+
self.max_requests = max_requests
|
|
227
|
+
self.window_seconds = window_seconds
|
|
228
|
+
self.requests: list[float] = []
|
|
229
|
+
self.lock = asyncio.Lock()
|
|
230
|
+
|
|
231
|
+
async def _clean_old_requests(self) -> None:
|
|
232
|
+
"""清除过期请求记录"""
|
|
233
|
+
now = time.time()
|
|
234
|
+
cutoff = now - self.window_seconds
|
|
235
|
+
self.requests = [t for t in self.requests if t > cutoff]
|
|
236
|
+
|
|
237
|
+
async def acquire(self) -> None:
|
|
238
|
+
"""获取许可"""
|
|
239
|
+
async with self.lock:
|
|
240
|
+
await self._clean_old_requests()
|
|
241
|
+
|
|
242
|
+
if len(self.requests) < self.max_requests:
|
|
243
|
+
self.requests.append(time.time())
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
oldest = self.requests[0]
|
|
247
|
+
wait_time = oldest + self.window_seconds - time.time()
|
|
248
|
+
if wait_time > 0:
|
|
249
|
+
await asyncio.sleep(wait_time)
|
|
250
|
+
|
|
251
|
+
await self._clean_old_requests()
|
|
252
|
+
self.requests.append(time.time())
|
|
253
|
+
|
|
254
|
+
async def try_acquire(self) -> bool:
|
|
255
|
+
"""非阻塞尝试获取许可"""
|
|
256
|
+
async with self.lock:
|
|
257
|
+
await self._clean_old_requests()
|
|
258
|
+
|
|
259
|
+
if len(self.requests) < self.max_requests:
|
|
260
|
+
self.requests.append(time.time())
|
|
261
|
+
return True
|
|
262
|
+
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class AdaptiveRateLimiter:
|
|
267
|
+
"""自适应速率限制器
|
|
268
|
+
|
|
269
|
+
根据 API 响应自动调整请求速率。
|
|
270
|
+
当检测到限流错误时自动降低速率,正常时逐步提升。
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
initial_rps: 初始速率(每秒请求数)
|
|
274
|
+
min_rps: 最小速率
|
|
275
|
+
max_rps: 最大速率
|
|
276
|
+
increase_factor: 正常时速率增加因子
|
|
277
|
+
decrease_factor: 触发限流时速率降低因子
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
def __init__(
|
|
281
|
+
self,
|
|
282
|
+
initial_rps: float = 0.5,
|
|
283
|
+
min_rps: float = 0.1,
|
|
284
|
+
max_rps: float = 2.0,
|
|
285
|
+
increase_factor: float = 1.1,
|
|
286
|
+
decrease_factor: float = 0.5,
|
|
287
|
+
):
|
|
288
|
+
self.current_rps = initial_rps
|
|
289
|
+
self.min_rps = min_rps
|
|
290
|
+
self.max_rps = max_rps
|
|
291
|
+
self.increase_factor = increase_factor
|
|
292
|
+
self.decrease_factor = decrease_factor
|
|
293
|
+
self.last_adjust_time = time.time()
|
|
294
|
+
self.lock = threading.Lock()
|
|
295
|
+
|
|
296
|
+
self._bucket = TokenBucket(initial_rps, burst=1)
|
|
297
|
+
|
|
298
|
+
def report_success(self) -> None:
|
|
299
|
+
"""报告成功调用,适当提高速率"""
|
|
300
|
+
with self.lock:
|
|
301
|
+
now = time.time()
|
|
302
|
+
if now - self.last_adjust_time >= 1.0:
|
|
303
|
+
self.current_rps = min(self.max_rps, self.current_rps * self.increase_factor)
|
|
304
|
+
self._bucket = TokenBucket(self.current_rps, burst=1)
|
|
305
|
+
self.last_adjust_time = now
|
|
306
|
+
|
|
307
|
+
def report_rate_limit(self) -> None:
|
|
308
|
+
"""报告限流错误,大幅降低速率"""
|
|
309
|
+
with self.lock:
|
|
310
|
+
self.current_rps = max(self.min_rps, self.current_rps * self.decrease_factor)
|
|
311
|
+
self._bucket = TokenBucket(self.current_rps, burst=1)
|
|
312
|
+
|
|
313
|
+
def report_server_error(self) -> None:
|
|
314
|
+
"""报告服务器错误,中等降低速率"""
|
|
315
|
+
with self.lock:
|
|
316
|
+
self.current_rps = max(self.min_rps, self.current_rps * 0.7)
|
|
317
|
+
self._bucket = TokenBucket(self.current_rps, burst=1)
|
|
318
|
+
|
|
319
|
+
def acquire(self, blocking: bool = True) -> bool:
|
|
320
|
+
"""获取令牌"""
|
|
321
|
+
return self._bucket.acquire(blocking)
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def current_rate(self) -> float:
|
|
325
|
+
"""当前速率"""
|
|
326
|
+
return self.current_rps
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
Provider 注册表
|
|
4
|
+
|
|
5
|
+
管理多 LLM Provider 配置,按 model 名路由到正确的 provider。
|
|
6
|
+
支持优先级排序和 fallback 机制。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ProviderConfig:
|
|
18
|
+
"""单个 Provider 配置"""
|
|
19
|
+
name: str
|
|
20
|
+
api_key: str
|
|
21
|
+
api_base: str
|
|
22
|
+
models: List[str] = field(default_factory=list)
|
|
23
|
+
extra_headers: Dict[str, str] = field(default_factory=dict)
|
|
24
|
+
priority: int = 1
|
|
25
|
+
timeout: int = 60
|
|
26
|
+
max_retries: int = 3
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ProviderRegistry:
|
|
30
|
+
"""Provider 注册表 — 管理多 Provider 配置,按 model 路由"""
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
self._providers: Dict[str, ProviderConfig] = {}
|
|
34
|
+
self._default_provider: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_settings(cls, agent_config: dict) -> "ProviderRegistry":
|
|
38
|
+
"""从 settings.json 的 agent 节点加载
|
|
39
|
+
|
|
40
|
+
支持两种模式:
|
|
41
|
+
1. 新模式:agent.providers 字典配置多 provider
|
|
42
|
+
2. 旧模式:agent.api_key + agent.api_base 单 provider(向后兼容)
|
|
43
|
+
"""
|
|
44
|
+
registry = cls()
|
|
45
|
+
providers_data = agent_config.get("providers", {})
|
|
46
|
+
|
|
47
|
+
for name, pconfig in providers_data.items():
|
|
48
|
+
registry.register(ProviderConfig(
|
|
49
|
+
name=name,
|
|
50
|
+
api_key=pconfig.get("api_key", ""),
|
|
51
|
+
api_base=pconfig.get("api_base", ""),
|
|
52
|
+
models=pconfig.get("models", []),
|
|
53
|
+
extra_headers=pconfig.get("extra_headers", {}),
|
|
54
|
+
priority=pconfig.get("priority", 1),
|
|
55
|
+
timeout=pconfig.get("timeout", 60),
|
|
56
|
+
max_retries=pconfig.get("max_retries", 3),
|
|
57
|
+
))
|
|
58
|
+
|
|
59
|
+
registry._default_provider = agent_config.get("provider")
|
|
60
|
+
|
|
61
|
+
# 向后兼容:无 providers 配置时,用顶层字段创建单 provider
|
|
62
|
+
if not providers_data and agent_config.get("api_key"):
|
|
63
|
+
default_name = registry._default_provider or "default"
|
|
64
|
+
model = agent_config.get("model", "")
|
|
65
|
+
registry.register(ProviderConfig(
|
|
66
|
+
name=default_name,
|
|
67
|
+
api_key=agent_config.get("api_key", ""),
|
|
68
|
+
api_base=agent_config.get("api_base", ""),
|
|
69
|
+
models=[model] if model else [],
|
|
70
|
+
priority=1,
|
|
71
|
+
timeout=agent_config.get("llm_timeout", 60),
|
|
72
|
+
max_retries=agent_config.get("llm_max_retries", 3),
|
|
73
|
+
))
|
|
74
|
+
registry._default_provider = default_name
|
|
75
|
+
|
|
76
|
+
return registry
|
|
77
|
+
|
|
78
|
+
def register(self, config: ProviderConfig) -> None:
|
|
79
|
+
"""注册一个 Provider"""
|
|
80
|
+
self._providers[config.name] = config
|
|
81
|
+
|
|
82
|
+
def get(self, name: str) -> Optional[ProviderConfig]:
|
|
83
|
+
"""获取指定名称的 Provider"""
|
|
84
|
+
return self._providers.get(name)
|
|
85
|
+
|
|
86
|
+
def resolve(self, model: Optional[str] = None) -> Optional[ProviderConfig]:
|
|
87
|
+
"""根据 model 名找到最优 provider
|
|
88
|
+
|
|
89
|
+
路由逻辑:
|
|
90
|
+
1. model=None → 返回默认 provider
|
|
91
|
+
2. 默认 provider 的 models 中匹配 → 优先返回
|
|
92
|
+
3. 所有 provider 中匹配 → 按 priority 排序(数字小优先)
|
|
93
|
+
4. 都找不到 → 返回默认 provider 兜底
|
|
94
|
+
"""
|
|
95
|
+
if not self._providers:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
# 1. model=None → 默认 provider
|
|
99
|
+
if model is None:
|
|
100
|
+
return self._get_default_or_first()
|
|
101
|
+
|
|
102
|
+
# 2. 默认 provider 优先
|
|
103
|
+
default = self._providers.get(self._default_provider)
|
|
104
|
+
if default and model in default.models:
|
|
105
|
+
return default
|
|
106
|
+
|
|
107
|
+
# 3. 所有 provider 中按 priority 排序
|
|
108
|
+
candidates = [
|
|
109
|
+
p for p in self._providers.values()
|
|
110
|
+
if model in p.models
|
|
111
|
+
]
|
|
112
|
+
if candidates:
|
|
113
|
+
candidates.sort(key=lambda p: p.priority)
|
|
114
|
+
return candidates[0]
|
|
115
|
+
|
|
116
|
+
# 4. 兜底:返回默认 provider
|
|
117
|
+
return self._get_default_or_first()
|
|
118
|
+
|
|
119
|
+
def _get_default_or_first(self) -> Optional[ProviderConfig]:
|
|
120
|
+
"""获取默认 provider,无默认则返回第一个"""
|
|
121
|
+
default = self._providers.get(self._default_provider)
|
|
122
|
+
if default:
|
|
123
|
+
return default
|
|
124
|
+
if self._providers:
|
|
125
|
+
return next(iter(self._providers.values()))
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
def list_providers(self) -> List[ProviderConfig]:
|
|
129
|
+
"""列出所有已注册的 Provider"""
|
|
130
|
+
return list(self._providers.values())
|
|
131
|
+
|
|
132
|
+
def get_models_map(self) -> Dict[str, List[str]]:
|
|
133
|
+
"""获取 provider → models 映射"""
|
|
134
|
+
return {
|
|
135
|
+
name: config.models
|
|
136
|
+
for name, config in self._providers.items()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
def get_client(self, config: ProviderConfig):
|
|
140
|
+
"""为指定 provider 创建 OpenAIClient"""
|
|
141
|
+
from QuantNodes.ai.llm.openai import OpenAIClient
|
|
142
|
+
return OpenAIClient(
|
|
143
|
+
api_key=config.api_key,
|
|
144
|
+
base_url=config.api_base,
|
|
145
|
+
timeout=config.timeout,
|
|
146
|
+
max_retries=config.max_retries,
|
|
147
|
+
extra_headers=config.extra_headers,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def get_default_client(self):
|
|
151
|
+
"""获取默认 provider 的 client"""
|
|
152
|
+
config = self._get_default_or_first()
|
|
153
|
+
if config:
|
|
154
|
+
return self.get_client(config)
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def default_provider_name(self) -> Optional[str]:
|
|
159
|
+
return self._default_provider
|
|
160
|
+
|
|
161
|
+
def __repr__(self) -> str:
|
|
162
|
+
providers_str = ", ".join(self._providers.keys())
|
|
163
|
+
return f"ProviderRegistry(default={self._default_provider}, providers=[{providers_str}])"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
Skill System - Phase 4.1
|
|
4
|
+
|
|
5
|
+
Skills: Strategy skills, Factor skills, Dream system
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .base import Skill, SkillCategory, SkillStatus, SkillMetadata, SkillResult
|
|
9
|
+
from .registry import SkillRegistry
|
|
10
|
+
from .loader import SkillLoader
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Skill",
|
|
14
|
+
"SkillCategory",
|
|
15
|
+
"SkillStatus",
|
|
16
|
+
"SkillMetadata",
|
|
17
|
+
"SkillResult",
|
|
18
|
+
"SkillRegistry",
|
|
19
|
+
"SkillLoader",
|
|
20
|
+
]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
Skill System Base Classes
|
|
4
|
+
|
|
5
|
+
Phase 4.1: Skill Infrastructure
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SkillCategory(Enum):
|
|
16
|
+
STRATEGY = "strategy"
|
|
17
|
+
FACTOR = "factor"
|
|
18
|
+
ANALYSIS = "analysis"
|
|
19
|
+
DREAM = "dream"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SkillStatus(Enum):
|
|
23
|
+
ACTIVE = "active"
|
|
24
|
+
DEPRECATED = "deprecated"
|
|
25
|
+
EXPERIMENTAL = "experimental"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class SkillMetadata:
|
|
30
|
+
name: str
|
|
31
|
+
description: str
|
|
32
|
+
category: SkillCategory
|
|
33
|
+
version: str = "1.0"
|
|
34
|
+
author: str = ""
|
|
35
|
+
tags: List[str] = field(default_factory=list)
|
|
36
|
+
examples: List[str] = field(default_factory=list)
|
|
37
|
+
dependencies: List[str] = field(default_factory=list)
|
|
38
|
+
status: SkillStatus = SkillStatus.ACTIVE
|
|
39
|
+
created_at: str = ""
|
|
40
|
+
updated_at: str = ""
|
|
41
|
+
|
|
42
|
+
def __post_init__(self):
|
|
43
|
+
if not self.created_at:
|
|
44
|
+
self.created_at = datetime.now().isoformat()
|
|
45
|
+
if not self.updated_at:
|
|
46
|
+
self.updated_at = self.created_at
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class SkillResult:
|
|
51
|
+
success: bool
|
|
52
|
+
data: Any = None
|
|
53
|
+
error: str = ""
|
|
54
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
55
|
+
execution_time_ms: float = 0.0
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> Dict:
|
|
58
|
+
return {
|
|
59
|
+
"success": self.success,
|
|
60
|
+
"data": self.data,
|
|
61
|
+
"error": self.error,
|
|
62
|
+
"metadata": self.metadata,
|
|
63
|
+
"execution_time_ms": self.execution_time_ms,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Skill(ABC):
|
|
68
|
+
"""Skill Abstract Base Class"""
|
|
69
|
+
|
|
70
|
+
def __init__(self):
|
|
71
|
+
self._metadata: Optional[SkillMetadata] = None
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def metadata(self) -> SkillMetadata:
|
|
76
|
+
"""Return skill metadata"""
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def name(self) -> str:
|
|
81
|
+
return self.metadata.name
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def description(self) -> str:
|
|
85
|
+
return self.metadata.description
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def category(self) -> SkillCategory:
|
|
89
|
+
return self.metadata.category
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
async def execute(self, context: Dict[str, Any]) -> SkillResult:
|
|
93
|
+
"""Execute the skill"""
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
def validate_context(
|
|
97
|
+
self, context: Dict[str, Any], required_keys: List[str]
|
|
98
|
+
) -> bool:
|
|
99
|
+
"""Validate if context contains required keys"""
|
|
100
|
+
return all(k in context for k in required_keys)
|
|
101
|
+
|
|
102
|
+
def get_example_prompts(self) -> List[str]:
|
|
103
|
+
"""Return example prompts"""
|
|
104
|
+
return self.metadata.examples
|
|
105
|
+
|
|
106
|
+
def to_tool_schema(self) -> Dict[str, Any]:
|
|
107
|
+
"""Convert to nanobot Tool Schema"""
|
|
108
|
+
return {
|
|
109
|
+
"name": self.name,
|
|
110
|
+
"description": self.description,
|
|
111
|
+
"parameters": self.get_parameters_schema(),
|
|
112
|
+
"category": self.category.value,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@abstractmethod
|
|
116
|
+
def get_parameters_schema(self) -> Dict[str, Any]:
|
|
117
|
+
"""Return parameter Schema"""
|
|
118
|
+
pass
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
Skill → Tool Bridge
|
|
4
|
+
|
|
5
|
+
将 Skill 自动转换为 Agent Tool 并注册到 ToolRegistry。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
|
|
11
|
+
from ..skills.base import Skill, SkillResult
|
|
12
|
+
from ..skills.registry import SkillRegistry
|
|
13
|
+
from ..tools.base import Tool
|
|
14
|
+
from ..tools.registry import ToolRegistry
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SkillToolAdapter(Tool):
|
|
20
|
+
"""将 Skill 适配为 Tool 接口"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, skill: Skill):
|
|
23
|
+
self._skill = skill
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def name(self) -> str:
|
|
27
|
+
return f"skill_{self._skill.metadata.name}"
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def description(self) -> str:
|
|
31
|
+
return self._skill.metadata.description
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def parameters(self) -> Dict[str, Any]:
|
|
35
|
+
return self._skill.get_parameters_schema()
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def read_only(self) -> bool:
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
async def execute(self, **kwargs: Any) -> Any:
|
|
42
|
+
try:
|
|
43
|
+
result: SkillResult = await self._skill.execute(kwargs)
|
|
44
|
+
return result.to_dict()
|
|
45
|
+
except Exception as e:
|
|
46
|
+
logger.error("Skill %s execution failed: %s", self._skill.name, e)
|
|
47
|
+
return {"success": False, "error": str(e), "data": None}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SkillToolBridge:
|
|
51
|
+
"""将 SkillRegistry 中的 Skill 注册为 Agent Tool"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, skill_registry: SkillRegistry, tool_registry: ToolRegistry):
|
|
54
|
+
self.skill_registry = skill_registry
|
|
55
|
+
self.tool_registry = tool_registry
|
|
56
|
+
|
|
57
|
+
def register_all(self) -> int:
|
|
58
|
+
"""将所有 Skill 注册为 Tool,返回注册数量"""
|
|
59
|
+
count = 0
|
|
60
|
+
for skill in self.skill_registry.list_all():
|
|
61
|
+
tool = SkillToolAdapter(skill)
|
|
62
|
+
self.tool_registry.register(tool)
|
|
63
|
+
count += 1
|
|
64
|
+
return count
|
|
65
|
+
|
|
66
|
+
def unregister_all(self) -> int:
|
|
67
|
+
"""移除所有 SkillToolAdapter"""
|
|
68
|
+
count = 0
|
|
69
|
+
for tool in list(self.tool_registry.list_tools()):
|
|
70
|
+
if isinstance(tool, SkillToolAdapter):
|
|
71
|
+
self.tool_registry.unregister(tool.name)
|
|
72
|
+
count += 1
|
|
73
|
+
return count
|