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.
Files changed (399) hide show
  1. QuantNodes/__init__.py +15 -0
  2. QuantNodes/__main__.py +14 -0
  3. QuantNodes/agent/__init__.py +158 -0
  4. QuantNodes/agent/agents/__init__.py +13 -0
  5. QuantNodes/agent/agents/definition.py +180 -0
  6. QuantNodes/agent/agents/manager.py +73 -0
  7. QuantNodes/agent/config/__init__.py +34 -0
  8. QuantNodes/agent/config/executor.py +958 -0
  9. QuantNodes/agent/config/loader.py +427 -0
  10. QuantNodes/agent/config/templates/bollinger_bands.yaml +84 -0
  11. QuantNodes/agent/config/templates/dual_ma.yaml +72 -0
  12. QuantNodes/agent/config/templates/empty.yaml +56 -0
  13. QuantNodes/agent/config/templates/mean_reversion.yaml +47 -0
  14. QuantNodes/agent/config/templates/mean_reversion_zscore.yaml +90 -0
  15. QuantNodes/agent/config/templates/momentum.yaml +81 -0
  16. QuantNodes/agent/config/templates/momentum_breakout.yaml +84 -0
  17. QuantNodes/agent/config/templates/rsi_strategy.yaml +72 -0
  18. QuantNodes/agent/config/templates/volume_price.yaml +86 -0
  19. QuantNodes/agent/config/types.py +156 -0
  20. QuantNodes/agent/config_mapper.py +293 -0
  21. QuantNodes/agent/core/__init__.py +19 -0
  22. QuantNodes/agent/core/dream.py +47 -0
  23. QuantNodes/agent/core/quant_dream.py +274 -0
  24. QuantNodes/agent/cron_jobs.py +314 -0
  25. QuantNodes/agent/nanobot_bridge.py +242 -0
  26. QuantNodes/agent/permission/__init__.py +30 -0
  27. QuantNodes/agent/permission/defaults.py +36 -0
  28. QuantNodes/agent/permission/evaluate.py +41 -0
  29. QuantNodes/agent/permission/models.py +59 -0
  30. QuantNodes/agent/permission/service.py +133 -0
  31. QuantNodes/agent/providers/__init__.py +11 -0
  32. QuantNodes/agent/providers/base.py +102 -0
  33. QuantNodes/agent/providers/quantnodes.py +610 -0
  34. QuantNodes/agent/providers/rate_limiter.py +326 -0
  35. QuantNodes/agent/providers/registry.py +163 -0
  36. QuantNodes/agent/skills/__init__.py +20 -0
  37. QuantNodes/agent/skills/base.py +118 -0
  38. QuantNodes/agent/skills/bridge.py +73 -0
  39. QuantNodes/agent/skills/factor/__init__.py +14 -0
  40. QuantNodes/agent/skills/factor/correlation.py +99 -0
  41. QuantNodes/agent/skills/factor/group_backtest.py +114 -0
  42. QuantNodes/agent/skills/factor/ic_analysis.py +106 -0
  43. QuantNodes/agent/skills/loader.py +107 -0
  44. QuantNodes/agent/skills/registry.py +105 -0
  45. QuantNodes/agent/skills/strategy/__init__.py +16 -0
  46. QuantNodes/agent/skills/strategy/bollinger.py +86 -0
  47. QuantNodes/agent/skills/strategy/dual_ma.py +82 -0
  48. QuantNodes/agent/skills/strategy/momentum.py +74 -0
  49. QuantNodes/agent/skills/strategy/rsi_reversal.py +99 -0
  50. QuantNodes/agent/skills_quant/__init__.py +14 -0
  51. QuantNodes/agent/skills_quant/backtest-analyze/SKILL.md +42 -0
  52. QuantNodes/agent/skills_quant/config-driven/SKILL.md +72 -0
  53. QuantNodes/agent/skills_quant/factor-research/SKILL.md +40 -0
  54. QuantNodes/agent/skills_quant/quant-dream/SKILL.md +55 -0
  55. QuantNodes/agent/skills_quant/risk-management/SKILL.md +45 -0
  56. QuantNodes/agent/skills_quant/strategy-design/SKILL.md +43 -0
  57. QuantNodes/agent/templates/__init__.py +4 -0
  58. QuantNodes/agent/tools/__init__.py +173 -0
  59. QuantNodes/agent/tools/_workspace.py +51 -0
  60. QuantNodes/agent/tools/alpha_backtest.py +328 -0
  61. QuantNodes/agent/tools/alpha_evaluate.py +493 -0
  62. QuantNodes/agent/tools/backtest.py +226 -0
  63. QuantNodes/agent/tools/base.py +133 -0
  64. QuantNodes/agent/tools/code_search.py +207 -0
  65. QuantNodes/agent/tools/config_backtest.py +401 -0
  66. QuantNodes/agent/tools/context.py +97 -0
  67. QuantNodes/agent/tools/dream_skill.py +77 -0
  68. QuantNodes/agent/tools/echo.py +38 -0
  69. QuantNodes/agent/tools/factor.py +231 -0
  70. QuantNodes/agent/tools/file_ops.py +201 -0
  71. QuantNodes/agent/tools/git_ops.py +190 -0
  72. QuantNodes/agent/tools/operator_lookup.py +218 -0
  73. QuantNodes/agent/tools/output_truncation.py +77 -0
  74. QuantNodes/agent/tools/path_check.py +43 -0
  75. QuantNodes/agent/tools/pipeline.py +62 -0
  76. QuantNodes/agent/tools/registry.py +150 -0
  77. QuantNodes/agent/tools/sandbox.py +62 -0
  78. QuantNodes/agent/tools/shell_safety.py +63 -0
  79. QuantNodes/agent/tools/strategy.py +106 -0
  80. QuantNodes/agent/tools/task.py +171 -0
  81. QuantNodes/agent/tools/web_fetch.py +142 -0
  82. QuantNodes/agent/tools/web_search.py +114 -0
  83. QuantNodes/agent/tools/wiki.py +370 -0
  84. QuantNodes/agent/utils/__init__.py +11 -0
  85. QuantNodes/agent/utils/helpers.py +43 -0
  86. QuantNodes/agent/utils/prompt_templates.py +30 -0
  87. QuantNodes/agent/workflows/__init__.py +20 -0
  88. QuantNodes/agent/workflows/implementations/__init__.py +8 -0
  89. QuantNodes/agent/workflows/implementations/alpha_gpt.py +508 -0
  90. QuantNodes/agent/workflows/implementations/mcts.py +442 -0
  91. QuantNodes/agent/workflows/parsers.py +44 -0
  92. QuantNodes/agent/workflows/registry.py +119 -0
  93. QuantNodes/agent/workflows/step_agent.py +219 -0
  94. QuantNodes/agent/workflows/tool.py +198 -0
  95. QuantNodes/ai/__init__.py +93 -0
  96. QuantNodes/ai/llm/__init__.py +75 -0
  97. QuantNodes/ai/llm/base.py +233 -0
  98. QuantNodes/ai/llm/decorators.py +281 -0
  99. QuantNodes/ai/llm/gateway.py +571 -0
  100. QuantNodes/ai/llm/null.py +76 -0
  101. QuantNodes/ai/llm/openai.py +435 -0
  102. QuantNodes/ai/optimizer.py +405 -0
  103. QuantNodes/ai/prompts/__init__.py +229 -0
  104. QuantNodes/ai/sandbox.py +371 -0
  105. QuantNodes/ai/sandbox_pandas_bridge.py +150 -0
  106. QuantNodes/ai/strategy_gen.py +396 -0
  107. QuantNodes/backtest/__init__.py +64 -0
  108. QuantNodes/backtest/backtest_node.py +188 -0
  109. QuantNodes/backtest/broker_node.py +378 -0
  110. QuantNodes/backtest/config_runner.py +397 -0
  111. QuantNodes/backtest/config_strategy.py +64 -0
  112. QuantNodes/backtest/risk_node.py +360 -0
  113. QuantNodes/backtest/strategy_node.py +268 -0
  114. QuantNodes/cache_node/__init__.py +19 -0
  115. QuantNodes/cache_node/base.py +244 -0
  116. QuantNodes/cache_node/cache_store.py +99 -0
  117. QuantNodes/cache_node/metadata.py +100 -0
  118. QuantNodes/cli/__init__.py +109 -0
  119. QuantNodes/cli/_helpers.py +511 -0
  120. QuantNodes/cli/command.py +110 -0
  121. QuantNodes/cli/commands/__init__.py +69 -0
  122. QuantNodes/cli/commands/agent.py +158 -0
  123. QuantNodes/cli/commands/alpha.py +951 -0
  124. QuantNodes/cli/commands/chat.py +38 -0
  125. QuantNodes/cli/commands/evolve.py +120 -0
  126. QuantNodes/cli/commands/factor.py +569 -0
  127. QuantNodes/cli/commands/init.py +190 -0
  128. QuantNodes/cli/commands/run.py +259 -0
  129. QuantNodes/cli/commands/serve.py +398 -0
  130. QuantNodes/cli/commands/version.py +120 -0
  131. QuantNodes/cli/enhanced.py +146 -0
  132. QuantNodes/conf_node/__init__.py +37 -0
  133. QuantNodes/conf_node/base.py +120 -0
  134. QuantNodes/conf_node/env_config.py +132 -0
  135. QuantNodes/conf_node/ini_config.py +70 -0
  136. QuantNodes/conf_node/json_config.py +69 -0
  137. QuantNodes/conf_node/yaml_config.py +78 -0
  138. QuantNodes/constants.py +17 -0
  139. QuantNodes/core/__init__.py +196 -0
  140. QuantNodes/core/_lookback_helpers.py +49 -0
  141. QuantNodes/core/ast_parser.py +198 -0
  142. QuantNodes/core/base.py +61 -0
  143. QuantNodes/core/cache_manager.py +344 -0
  144. QuantNodes/core/cache_utils.py +150 -0
  145. QuantNodes/core/cond_builder.py +53 -0
  146. QuantNodes/core/config.py +170 -0
  147. QuantNodes/core/constants.py +48 -0
  148. QuantNodes/core/control.py +412 -0
  149. QuantNodes/core/data_preprocessing.py +453 -0
  150. QuantNodes/core/data_source.py +46 -0
  151. QuantNodes/core/events.py +178 -0
  152. QuantNodes/core/evolution/__init__.py +22 -0
  153. QuantNodes/core/evolution/loop.py +583 -0
  154. QuantNodes/core/evolution/operators.py +289 -0
  155. QuantNodes/core/evolution/settings.py +44 -0
  156. QuantNodes/core/expression.py +841 -0
  157. QuantNodes/core/feedback/__init__.py +38 -0
  158. QuantNodes/core/feedback/channels.py +182 -0
  159. QuantNodes/core/feedback/collector.py +91 -0
  160. QuantNodes/core/feedback/dataclass.py +239 -0
  161. QuantNodes/core/feedback/llm_judge.py +138 -0
  162. QuantNodes/core/knowledge/__init__.py +69 -0
  163. QuantNodes/core/knowledge/knowledge_base.py +217 -0
  164. QuantNodes/core/knowledge/lineage_compress.py +196 -0
  165. QuantNodes/core/knowledge/lineage_expand.py +123 -0
  166. QuantNodes/core/knowledge/metrics/__init__.py +43 -0
  167. QuantNodes/core/knowledge/metrics/evaluator.py +176 -0
  168. QuantNodes/core/knowledge/metrics/metrics.py +220 -0
  169. QuantNodes/core/knowledge/rag_prompt.py +196 -0
  170. QuantNodes/core/knowledge/retriever.py +209 -0
  171. QuantNodes/core/lambda_node.py +81 -0
  172. QuantNodes/core/monitoring/__init__.py +22 -0
  173. QuantNodes/core/monitoring/collector.py +292 -0
  174. QuantNodes/core/monitoring/dashboard.py +365 -0
  175. QuantNodes/core/node.py +375 -0
  176. QuantNodes/core/pandas_utils.py +504 -0
  177. QuantNodes/core/parallel/__init__.py +15 -0
  178. QuantNodes/core/parallel/worker.py +140 -0
  179. QuantNodes/core/parallel/worker_process.py +265 -0
  180. QuantNodes/core/path_utils.py +73 -0
  181. QuantNodes/core/pipeline.py +328 -0
  182. QuantNodes/core/plugin.py +135 -0
  183. QuantNodes/core/quality_gate/__init__.py +32 -0
  184. QuantNodes/core/quality_gate/complexity.py +94 -0
  185. QuantNodes/core/quality_gate/consistency.py +26 -0
  186. QuantNodes/core/quality_gate/node.py +97 -0
  187. QuantNodes/core/quality_gate/redundancy.py +51 -0
  188. QuantNodes/core/quality_gate/settings.py +43 -0
  189. QuantNodes/core/quality_gate/zoo.py +98 -0
  190. QuantNodes/core/serializable.py +116 -0
  191. QuantNodes/core/serialization.py +673 -0
  192. QuantNodes/core/tools.py +333 -0
  193. QuantNodes/core/trajectory/__init__.py +25 -0
  194. QuantNodes/core/trajectory/entry.py +116 -0
  195. QuantNodes/core/trajectory/lineage.py +67 -0
  196. QuantNodes/core/trajectory/pool.py +211 -0
  197. QuantNodes/core/trajectory/selector.py +140 -0
  198. QuantNodes/core/visualization/__init__.py +33 -0
  199. QuantNodes/core/visualization/builder.py +233 -0
  200. QuantNodes/core/visualization/gate_breakdown.py +140 -0
  201. QuantNodes/core/visualization/lineage_dag.py +203 -0
  202. QuantNodes/core/visualization/metric_distribution.py +125 -0
  203. QuantNodes/core/visualization/report.py +68 -0
  204. QuantNodes/database_node/__init__.py +69 -0
  205. QuantNodes/database_node/base.py +135 -0
  206. QuantNodes/database_node/clickhouse_node.py +272 -0
  207. QuantNodes/database_node/csv_node.py +83 -0
  208. QuantNodes/database_node/duckdb_node.py +86 -0
  209. QuantNodes/database_node/factory.py +83 -0
  210. QuantNodes/database_node/mysql_node.py +100 -0
  211. QuantNodes/database_node/parquet_node.py +75 -0
  212. QuantNodes/database_node/sqlite_node.py +67 -0
  213. QuantNodes/factor_node/__init__.py +50 -0
  214. QuantNodes/factor_node/factor.py +563 -0
  215. QuantNodes/factor_node/factor_db.py +421 -0
  216. QuantNodes/factor_node/factor_functions/__init__.py +252 -0
  217. QuantNodes/factor_node/factor_functions/_helpers.py +358 -0
  218. QuantNodes/factor_node/factor_functions/_helpers_debug.py +317 -0
  219. QuantNodes/factor_node/factor_functions/composite_ops.py +136 -0
  220. QuantNodes/factor_node/factor_functions/math_ops.py +433 -0
  221. QuantNodes/factor_node/factor_functions/section_ops.py +290 -0
  222. QuantNodes/factor_node/factor_functions/talib_ops.py +1293 -0
  223. QuantNodes/factor_node/factor_functions/time_ops.py +535 -0
  224. QuantNodes/factor_node/factor_operation.py +1115 -0
  225. QuantNodes/factor_node/factor_table.py +1073 -0
  226. QuantNodes/factor_node/quant_nodes_object.py +60 -0
  227. QuantNodes/mcp_server/__init__.py +27 -0
  228. QuantNodes/mcp_server/__main__.py +4 -0
  229. QuantNodes/mcp_server/server.py +272 -0
  230. QuantNodes/methods/__init__.py +28 -0
  231. QuantNodes/methods/pipeline.py +100 -0
  232. QuantNodes/methods/sandbox.py +102 -0
  233. QuantNodes/monitor/__init__.py +27 -0
  234. QuantNodes/monitor/agent_tools/__init__.py +5 -0
  235. QuantNodes/monitor/agent_tools/monitor_tool.py +98 -0
  236. QuantNodes/monitor/agent_tools/schedule_tool.py +98 -0
  237. QuantNodes/monitor/agent_tools/version_tool.py +133 -0
  238. QuantNodes/monitor/monitor/__init__.py +6 -0
  239. QuantNodes/monitor/monitor/alerter.py +60 -0
  240. QuantNodes/monitor/monitor/collector.py +164 -0
  241. QuantNodes/monitor/monitor/dashboard.py +115 -0
  242. QuantNodes/monitor/monitor/drift.py +190 -0
  243. QuantNodes/monitor/scheduler/__init__.py +4 -0
  244. QuantNodes/monitor/scheduler/runner.py +133 -0
  245. QuantNodes/monitor/scheduler/scheduler.py +184 -0
  246. QuantNodes/monitor/storage/__init__.py +16 -0
  247. QuantNodes/monitor/storage/models.py +70 -0
  248. QuantNodes/monitor/storage/repository.py +407 -0
  249. QuantNodes/monitor/version/__init__.py +4 -0
  250. QuantNodes/monitor/version/diff.py +81 -0
  251. QuantNodes/monitor/version/version_manager.py +182 -0
  252. QuantNodes/operator_node/__init__.py +28 -0
  253. QuantNodes/operator_node/base.py +97 -0
  254. QuantNodes/operator_node/query_node.py +129 -0
  255. QuantNodes/operator_node/sql_builder.py +125 -0
  256. QuantNodes/operator_node/sql_utils.py +172 -0
  257. QuantNodes/operator_node/transform.py +130 -0
  258. QuantNodes/operators/__init__.py +90 -0
  259. QuantNodes/operators/_engine.py +108 -0
  260. QuantNodes/operators/composite.py +161 -0
  261. QuantNodes/operators/composite_dag.py +667 -0
  262. QuantNodes/operators/composite_dag_ops.py +343 -0
  263. QuantNodes/operators/composite_dag_pandas_ops.py +382 -0
  264. QuantNodes/operators/custom.py +408 -0
  265. QuantNodes/operators/facade.py +164 -0
  266. QuantNodes/operators/math.py +163 -0
  267. QuantNodes/operators/proxy.py +29 -0
  268. QuantNodes/operators/registry.py +144 -0
  269. QuantNodes/operators/section.py +99 -0
  270. QuantNodes/operators/talib.py +757 -0
  271. QuantNodes/operators/templates.py +95 -0
  272. QuantNodes/operators/time_series.py +136 -0
  273. QuantNodes/prompts/__init__.py +20 -0
  274. QuantNodes/prompts/backtest/__init__.py +12 -0
  275. QuantNodes/prompts/backtest/factor_based.py +86 -0
  276. QuantNodes/prompts/backtest/standard.py +73 -0
  277. QuantNodes/prompts/factor/__init__.py +14 -0
  278. QuantNodes/prompts/factor/correlation.py +77 -0
  279. QuantNodes/prompts/factor/group_backtest.py +86 -0
  280. QuantNodes/prompts/factor/ic_analysis.py +91 -0
  281. QuantNodes/prompts/strategy/__init__.py +18 -0
  282. QuantNodes/prompts/strategy/market_neutral.py +96 -0
  283. QuantNodes/prompts/strategy/mean_reversion.py +107 -0
  284. QuantNodes/prompts/strategy/momentum.py +160 -0
  285. QuantNodes/prompts/strategy/pairs_trading.py +107 -0
  286. QuantNodes/prompts/strategy/trend_following.py +96 -0
  287. QuantNodes/research/README.md +106 -0
  288. QuantNodes/research/__init__.py +154 -0
  289. QuantNodes/research/_legacy_3c/__init__.py +61 -0
  290. QuantNodes/research/_legacy_3c/auto_researcher.py +289 -0
  291. QuantNodes/research/_legacy_3c/factor_evaluator.py +560 -0
  292. QuantNodes/research/_legacy_3c/factor_miner.py +318 -0
  293. QuantNodes/research/_legacy_3c/mcts_search.py +324 -0
  294. QuantNodes/research/factor_test/__init__.py +25 -0
  295. QuantNodes/research/factor_test/config.py +184 -0
  296. QuantNodes/research/factor_test/config_builder.py +276 -0
  297. QuantNodes/research/factor_test/e2e/data_prep.py +163 -0
  298. QuantNodes/research/factor_test/e2e/run_evolution_e2e.py +309 -0
  299. QuantNodes/research/factor_test/evolution_adapter.py +231 -0
  300. QuantNodes/research/factor_test/feedback_wrapper.py +102 -0
  301. QuantNodes/research/factor_test/ifind_db/__init__.py +7 -0
  302. QuantNodes/research/factor_test/ifind_db/fetcher.py +224 -0
  303. QuantNodes/research/factor_test/ifind_db/ifind_database.py +689 -0
  304. QuantNodes/research/factor_test/nodes/__init__.py +1 -0
  305. QuantNodes/research/factor_test/nodes/_base.py +91 -0
  306. QuantNodes/research/factor_test/nodes/adjust_date_node.py +48 -0
  307. QuantNodes/research/factor_test/nodes/configs.py +240 -0
  308. QuantNodes/research/factor_test/nodes/factor_neutralize_node.py +87 -0
  309. QuantNodes/research/factor_test/nodes/factor_preprocess_node.py +222 -0
  310. QuantNodes/research/factor_test/nodes/factor_score_node.py +141 -0
  311. QuantNodes/research/factor_test/nodes/factor_test_report_node.py +153 -0
  312. QuantNodes/research/factor_test/nodes/group_analyzer_node.py +317 -0
  313. QuantNodes/research/factor_test/nodes/ic_analyzer_node.py +112 -0
  314. QuantNodes/research/factor_test/nodes/load_data_node.py +100 -0
  315. QuantNodes/research/factor_test/nodes/long_short_node.py +93 -0
  316. QuantNodes/research/factor_test/nodes/neutralizers.py +222 -0
  317. QuantNodes/research/factor_test/nodes/preprocess_strategies.py +277 -0
  318. QuantNodes/research/factor_test/nodes/risk_correlation_node.py +112 -0
  319. QuantNodes/research/factor_test/nodes/sample_pool_filter_node.py +110 -0
  320. QuantNodes/research/factor_test/nodes/tradability_filter_node.py +92 -0
  321. QuantNodes/research/factor_test/pipeline_runner.py +305 -0
  322. QuantNodes/research/factor_test/pipeline_spec.py +216 -0
  323. QuantNodes/research/factor_test/utils/__init__.py +26 -0
  324. QuantNodes/research/factor_test/utils/constants.py +86 -0
  325. QuantNodes/research/factor_test/utils/data_loader.py +141 -0
  326. QuantNodes/research/factor_test/utils/date_utils.py +232 -0
  327. QuantNodes/research/factor_test/utils/file_loaders.py +150 -0
  328. QuantNodes/research/factor_test/utils/labels.py +37 -0
  329. QuantNodes/research/factor_test/utils/metrics_extractor.py +55 -0
  330. QuantNodes/research/factor_test/utils/performance_metrics.py +175 -0
  331. QuantNodes/research/factor_test/utils/safe_load.py +106 -0
  332. QuantNodes/research/quant_alpha/CHANGELOG.md +80 -0
  333. QuantNodes/research/quant_alpha/README.md +142 -0
  334. QuantNodes/research/quant_alpha/__init__.py +45 -0
  335. QuantNodes/research/quant_alpha/adapters/__init__.py +99 -0
  336. QuantNodes/research/quant_alpha/adapters/calculator.py +503 -0
  337. QuantNodes/research/quant_alpha/adapters/expression.py +387 -0
  338. QuantNodes/research/quant_alpha/alpha101_design/__init__.py +50 -0
  339. QuantNodes/research/quant_alpha/alpha101_design/few_shot_examples.py +243 -0
  340. QuantNodes/research/quant_alpha/alpha101_design/philosophy.py +474 -0
  341. QuantNodes/research/quant_alpha/alpha158_design/__init__.py +63 -0
  342. QuantNodes/research/quant_alpha/alpha158_design/few_shot_examples.py +219 -0
  343. QuantNodes/research/quant_alpha/alpha158_design/philosophy.py +240 -0
  344. QuantNodes/research/quant_alpha/evaluation/__init__.py +47 -0
  345. QuantNodes/research/quant_alpha/evaluation/baselines/__init__.py +8 -0
  346. QuantNodes/research/quant_alpha/evaluation/baselines/g1_handcrafted.py +135 -0
  347. QuantNodes/research/quant_alpha/evaluation/baselines/g2_llm_only.py +269 -0
  348. QuantNodes/research/quant_alpha/evaluation/baselines/g3_alpha_gpt.py +152 -0
  349. QuantNodes/research/quant_alpha/evaluation/clickhouse_data_loader.py +227 -0
  350. QuantNodes/research/quant_alpha/evaluation/contracts.py +376 -0
  351. QuantNodes/research/quant_alpha/evaluation/evaluators/__init__.py +6 -0
  352. QuantNodes/research/quant_alpha/evaluation/evaluators/polars_evaluator.py +545 -0
  353. QuantNodes/research/quant_alpha/evaluation/mock_data_loader.py +226 -0
  354. QuantNodes/research/quant_alpha/evaluation/runner.py +243 -0
  355. QuantNodes/research/quant_alpha/llm/__init__.py +38 -0
  356. QuantNodes/research/quant_alpha/llm/parser.py +681 -0
  357. QuantNodes/research/quant_alpha/logic_driven_pipeline.py +411 -0
  358. QuantNodes/research/quant_alpha/logic_mining/__init__.py +74 -0
  359. QuantNodes/research/quant_alpha/logic_mining/compiler.py +457 -0
  360. QuantNodes/research/quant_alpha/logic_mining/generator.py +366 -0
  361. QuantNodes/research/quant_alpha/logic_mining/models.py +252 -0
  362. QuantNodes/research/quant_alpha/logic_mining/parser.py +287 -0
  363. QuantNodes/research/quant_alpha/logic_mining/pipelines.py +297 -0
  364. QuantNodes/research/quant_alpha/logic_mining/sources.py +149 -0
  365. QuantNodes/research/quant_alpha/mcts/__init__.py +66 -0
  366. QuantNodes/research/quant_alpha/mcts/cache.py +262 -0
  367. QuantNodes/research/quant_alpha/mcts/extension_ops.py +320 -0
  368. QuantNodes/research/quant_alpha/mcts/feedback.py +825 -0
  369. QuantNodes/research/quant_alpha/mcts/op_prior.py +180 -0
  370. QuantNodes/research/quant_alpha/mcts/search.py +540 -0
  371. QuantNodes/research/quant_alpha/mcts/tree.py +201 -0
  372. QuantNodes/research/quant_alpha/operator_vocab/__init__.py +50 -0
  373. QuantNodes/research/quant_alpha/operator_vocab/config.py +54 -0
  374. QuantNodes/research/quant_alpha/operator_vocab/metadata.py +263 -0
  375. QuantNodes/research/quant_alpha/operator_vocab/vocabulary.py +481 -0
  376. QuantNodes/research/quant_alpha/pipeline.py +1027 -0
  377. QuantNodes/research/quant_alpha/types/__init__.py +27 -0
  378. QuantNodes/research/quant_alpha/types/constants.py +28 -0
  379. QuantNodes/research/quant_alpha/types/state.py +205 -0
  380. QuantNodes/research/quant_alpha/workflow/__init__.py +32 -0
  381. QuantNodes/research/quant_alpha/workflow/alpha_gpt.py +911 -0
  382. QuantNodes/research/quant_alpha/workflow/alpha_logics.py +416 -0
  383. QuantNodes/research/quant_alpha/workflow/state.py +27 -0
  384. QuantNodes/research/report_reproducer.py +485 -0
  385. QuantNodes/research/wiki.py +1155 -0
  386. QuantNodes/symbolic/__init__.py +51 -0
  387. QuantNodes/symbolic/compiler.py +113 -0
  388. QuantNodes/symbolic/dialect.py +260 -0
  389. QuantNodes/symbolic/executor.py +147 -0
  390. QuantNodes/symbolic/expression.py +234 -0
  391. QuantNodes/symbolic/functions.py +433 -0
  392. QuantNodes/symbolic/optimizer.py +165 -0
  393. QuantNodes/ui_node/__init__.py +30 -0
  394. QuantNodes/ui_node/base.py +222 -0
  395. quantnodes-3.0.0.dist-info/METADATA +463 -0
  396. quantnodes-3.0.0.dist-info/RECORD +399 -0
  397. quantnodes-3.0.0.dist-info/WHEEL +5 -0
  398. quantnodes-3.0.0.dist-info/entry_points.txt +24 -0
  399. quantnodes-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,314 @@
1
+ # coding=utf-8
2
+ """QuantNodes-specific cron jobs.
3
+
4
+ v3.0.0 Stage 5.5: Layer quant-domain periodic tasks on top of nanobot's
5
+ upstream ``CronService``. We register three system jobs that run on a
6
+ fixed schedule inside the in-process nanobot runtime:
7
+
8
+ 1. **Daily 16:30** (Asia/Shanghai) — ``quant-daily-recap``
9
+ Factor IC recalc + backtest result archival + a summary message
10
+ delivered to the configured channel (e.g. Feishu group chat).
11
+
12
+ 2. **Weekly Sunday 22:00** (Asia/Shanghai) — ``quant-weekly-review``
13
+ Factor performance report (IC / ICIR / decay) + risk attribution
14
+ summary across the live strategy pool.
15
+
16
+ 3. **Monthly 1st 02:00** (Asia/Shanghai) — ``quant-monthly-strategy-pool``
17
+ Wiki incremental index + strategy-pool monthly review (best/worst,
18
+ capacity check, deprecation candidates).
19
+
20
+ All three are added via ``cron_service.add_job(...)`` so users can:
21
+
22
+ - Inspect them with ``GET /api/agent/cron`` (planned, see
23
+ ``api/routers/agent.py``).
24
+ - Disable / re-enable / delete them like any other job
25
+ (``CronService.remove_job`` refuses to remove ``system_event`` jobs,
26
+ but ours are ``agent_turn`` jobs and are user-removable).
27
+ - Override the schedule / message via env vars (see
28
+ ``build_quant_cron_jobs_from_env`` below).
29
+
30
+ We deliberately keep the **content** of each job a single concise message —
31
+ the agent has the quant tools (``factor``, ``wiki``, ``strategy``,
32
+ ``backtest``) at its disposal and knows how to fan out into a proper
33
+ report. This matches the upstream design: cron payloads are LLM
34
+ prompts, not pre-baked Python scripts.
35
+
36
+ Reference:
37
+
38
+ - ``nanobot.cron.service.CronService`` — add_job / register_system_job
39
+ - ``nanobot.cron.types.CronSchedule`` — kind: at | every | cron
40
+ - ``nanobot.cron.types.CronPayload`` — kind: system_event | agent_turn
41
+ - ``QuantNodes/agent/core/quant_dream.py`` — Dream hook (separate
42
+ periodic memory consolidation; not part of cron jobs)
43
+ """
44
+
45
+ from __future__ import annotations
46
+
47
+ import logging
48
+ import os
49
+ from dataclasses import dataclass
50
+ from typing import Any, Callable, List, Optional
51
+
52
+ logger = logging.getLogger(__name__)
53
+
54
+
55
+ # Default cron expressions in Asia/Shanghai (the team's working TZ).
56
+ # Each entry pairs a job ``name`` with a default ``cron_expr`` and the
57
+ # prompt template that the agent runs on each tick. Users override any
58
+ # of these via env vars (see ``build_quant_cron_jobs_from_env``).
59
+ DEFAULT_TZ = "Asia/Shanghai"
60
+
61
+
62
+ @dataclass(frozen=True)
63
+ class QuantCronJob:
64
+ """A quant-domain periodic task definition.
65
+
66
+ Fields mirror what ``CronService.add_job`` consumes (we keep the
67
+ declaration local to avoid an import cycle through nanobot at module
68
+ load time — the optional dep).
69
+ """
70
+
71
+ name: str
72
+ cron_expr: str
73
+ message: str
74
+ enabled: bool = True
75
+ deliver: bool = True
76
+ channel: Optional[str] = None
77
+ session_key: Optional[str] = None
78
+ description: str = ""
79
+
80
+
81
+ # ----------------------------------------------------------------------------
82
+ # Default job definitions
83
+ # ----------------------------------------------------------------------------
84
+
85
+ DAILY_RECAP_JOB = QuantCronJob(
86
+ name="quant-daily-recap",
87
+ cron_expr="30 16 * * 1-5", # 16:30 Mon–Fri (skip weekends)
88
+ message=(
89
+ "执行日终复盘任务:\n"
90
+ "1. 调 factor 工具对核心因子池做今日 IC / ICIR 重算\n"
91
+ "2. 调 backtest 工具对今日有变动的策略跑一次回测,确认无过拟合 / "
92
+ "未来函数 / 手续费侵蚀\n"
93
+ "3. 调 wiki 工具把今日新增/变更的因子和策略写入 Wiki\n"
94
+ "4. 输出一段 ≤200 字的日终摘要(含因子表现 / 风险归因 / 明日关注点)"
95
+ ),
96
+ enabled=True,
97
+ deliver=True,
98
+ session_key="cron:quant-daily-recap",
99
+ description=(
100
+ "Daily 16:30 Mon-Fri — factor IC recalc + backtest archive + Wiki update."
101
+ ),
102
+ )
103
+
104
+
105
+ WEEKLY_REVIEW_JOB = QuantCronJob(
106
+ name="quant-weekly-review",
107
+ cron_expr="0 22 * * 0", # 22:00 every Sunday
108
+ message=(
109
+ "执行周度复盘任务:\n"
110
+ "1. 调 factor 工具生成核心因子过去 5 个交易日的 IC / 衰减 / 换手率报告\n"
111
+ "2. 调 strategy 工具对策略池每个策略拉一次本周净值 / 风险归因\n"
112
+ "3. 调 wiki 工具把本周因子表现 / 策略表现 / 风险事件写入 Wiki 周报\n"
113
+ "4. 输出一段 ≤300 字的周度摘要(含因子池变化 / 策略池表现 / 风险事件)"
114
+ ),
115
+ enabled=True,
116
+ deliver=True,
117
+ session_key="cron:quant-weekly-review",
118
+ description=(
119
+ "Weekly Sunday 22:00 — factor performance report + risk attribution + Wiki weekly."
120
+ ),
121
+ )
122
+
123
+
124
+ MONTHLY_STRATEGY_POOL_JOB = QuantCronJob(
125
+ name="quant-monthly-strategy-pool",
126
+ cron_expr="0 2 1 * *", # 02:00 on the 1st of every month
127
+ message=(
128
+ "执行月度策略池评审任务:\n"
129
+ "1. 调 wiki 工具做 Wiki 增量索引(核对因子 / 策略 / 回测报告三类文档数量与覆盖率)\n"
130
+ "2. 调 strategy 工具对策略池每个策略做月度表现 / 容量 / 衰减评估\n"
131
+ "3. 识别本月的 top-3 / bottom-3 策略,列出待下线 / 待重构的策略\n"
132
+ "4. 调 wiki 工具把评审结论写入 Wiki 的 strategy_review.md\n"
133
+ "5. 输出一段 ≤400 字的月度摘要(含策略池变化 / 新增下线 / 容量评估)"
134
+ ),
135
+ enabled=True,
136
+ deliver=True,
137
+ session_key="cron:quant-monthly-strategy-pool",
138
+ description=(
139
+ "Monthly 1st 02:00 — Wiki incremental + strategy-pool monthly review."
140
+ ),
141
+ )
142
+
143
+
144
+ DEFAULT_QUANT_CRON_JOBS: List[QuantCronJob] = [
145
+ DAILY_RECAP_JOB,
146
+ WEEKLY_REVIEW_JOB,
147
+ MONTHLY_STRATEGY_POOL_JOB,
148
+ ]
149
+
150
+
151
+ # ----------------------------------------------------------------------------
152
+ # Env-var overrides
153
+ # ----------------------------------------------------------------------------
154
+
155
+ # Each env var follows the pattern ``QUANTNODES__CRON__<NAME>__<FIELD>`` where
156
+ # ``<NAME>`` is uppercased job name and ``<FIELD>`` is one of:
157
+ # - ENABLED (bool: "1"/"true"/"false")
158
+ # - CRON_EXPR (cron expression override)
159
+ # - MESSAGE (full message override)
160
+ # - DELIVER (bool: "1"/"true"/"false")
161
+ # - CHANNEL (channel name override; e.g. "feishu")
162
+ #
163
+ # Example:
164
+ # QUANTNODES__CRON__QUANT_DAILY_RECAP__ENABLED=false
165
+ # QUANTNODES__CRON__QUANT_WEEKLY_REVIEW__CRON_EXPR="0 20 * * 0"
166
+
167
+
168
+ def _env_flag(name: str, default: bool) -> bool:
169
+ raw = os.environ.get(name, "").strip().lower()
170
+ if not raw:
171
+ return default
172
+ return raw in ("1", "true", "yes", "on")
173
+
174
+
175
+ def _apply_env_overrides(jobs: List[QuantCronJob]) -> List[QuantCronJob]:
176
+ """Apply per-job env-var overrides to a copy of ``jobs``.
177
+
178
+ Non-destructive: returns new ``QuantCronJob`` instances. Unknown env
179
+ vars are ignored. To disable a job, set its ``ENABLED=false`` env var.
180
+ """
181
+ overridden: List[QuantCronJob] = []
182
+ for job in jobs:
183
+ prefix = f"QUANTNODES__CRON__{job.name.replace('-', '_').upper()}__"
184
+ new_enabled = _env_flag(prefix + "ENABLED", job.enabled)
185
+ new_deliver = _env_flag(prefix + "DELIVER", job.deliver)
186
+ new_cron = os.environ.get(prefix + "CRON_EXPR", job.cron_expr).strip() or job.cron_expr
187
+ new_msg = os.environ.get(prefix + "MESSAGE", job.message) or job.message
188
+ new_chan = os.environ.get(prefix + "CHANNEL", job.channel or "") or job.channel
189
+
190
+ if (
191
+ new_enabled == job.enabled
192
+ and new_deliver == job.deliver
193
+ and new_cron == job.cron_expr
194
+ and new_msg == job.message
195
+ and new_chan == job.channel
196
+ ):
197
+ overridden.append(job)
198
+ else:
199
+ overridden.append(
200
+ QuantCronJob(
201
+ name=job.name,
202
+ cron_expr=new_cron,
203
+ message=new_msg,
204
+ enabled=new_enabled,
205
+ deliver=new_deliver,
206
+ channel=new_chan,
207
+ session_key=job.session_key,
208
+ description=job.description,
209
+ )
210
+ )
211
+ return overridden
212
+
213
+
214
+ def build_quant_cron_jobs_from_env(
215
+ base_jobs: Optional[List[QuantCronJob]] = None,
216
+ ) -> List[QuantCronJob]:
217
+ """Return the default list of quant cron jobs, with env-var overrides.
218
+
219
+ Always returns a fresh list (safe to mutate). Filters out jobs that
220
+ the user has explicitly disabled via env (``ENABLED=false``).
221
+ """
222
+ base = list(base_jobs or DEFAULT_QUANT_CRON_JOBS)
223
+ merged = _apply_env_overrides(base)
224
+ enabled = [j for j in merged if j.enabled]
225
+ if len(enabled) != len(merged):
226
+ logger.info(
227
+ "build_quant_cron_jobs_from_env: disabled %d cron job(s) via env: %s",
228
+ len(merged) - len(enabled),
229
+ ", ".join(j.name for j in merged if not j.enabled),
230
+ )
231
+ return enabled
232
+
233
+
234
+ # ----------------------------------------------------------------------------
235
+ # Registration helper
236
+ # ----------------------------------------------------------------------------
237
+
238
+ def register_quant_cron_jobs(cron_service: Any) -> List[str]:
239
+ """Register all enabled quant cron jobs on a nanobot ``CronService``.
240
+
241
+ ``cron_service`` is expected to be a ``nanobot.cron.service.CronService``
242
+ instance. We use ``register_system_job`` (idempotent on restart) so
243
+ that on every process restart the jobs are re-registered without
244
+ duplicating. ``system_event`` is the right payload kind for our
245
+ internal quant jobs — they cannot be removed via the public
246
+ ``remove_job`` API and they survive config edits.
247
+
248
+ Returns the list of registered job IDs in the order they were added.
249
+
250
+ Lazy import of ``nanobot.cron.types`` to keep this module importable
251
+ even when ``nanobot-ai`` is not installed (the optional dep).
252
+ """
253
+ try:
254
+ from nanobot.cron.types import (
255
+ CronJob,
256
+ CronJobState,
257
+ CronPayload,
258
+ CronSchedule,
259
+ )
260
+ except ImportError as e: # pragma: no cover - exercised in [agent] installs
261
+ raise NanobotNotInstalledForCron(str(e)) from e
262
+
263
+ jobs = build_quant_cron_jobs_from_env()
264
+ registered: List[str] = []
265
+ for job in jobs:
266
+ cron_job = CronJob(
267
+ id=f"quant-{job.name}", # deterministic id for idempotency
268
+ name=job.name,
269
+ enabled=job.enabled,
270
+ schedule=CronSchedule(
271
+ kind="cron",
272
+ expr=job.cron_expr,
273
+ tz=DEFAULT_TZ,
274
+ ),
275
+ payload=CronPayload(
276
+ kind="system_event",
277
+ message=job.message,
278
+ deliver=job.deliver,
279
+ channel=job.channel,
280
+ session_key=job.session_key,
281
+ ),
282
+ state=CronJobState(),
283
+ )
284
+ cron_service.register_system_job(cron_job)
285
+ registered.append(cron_job.id)
286
+ logger.info(
287
+ "Registered quant cron job '%s' (cron='%s', tz=%s)",
288
+ cron_job.name,
289
+ job.cron_expr,
290
+ DEFAULT_TZ,
291
+ )
292
+ return registered
293
+
294
+
295
+ class NanobotNotInstalledForCron(ImportError):
296
+ """Raised when ``register_quant_cron_jobs`` is called without nanobot.
297
+
298
+ Subclasses ``ImportError`` so callers (e.g. ``NanobotRuntime._build_components``)
299
+ can either catch it as ``ImportError`` for graceful degradation or as
300
+ the specific subclass for a clearer error message.
301
+ """
302
+
303
+
304
+ __all__ = [
305
+ "DEFAULT_TZ",
306
+ "QuantCronJob",
307
+ "DAILY_RECAP_JOB",
308
+ "WEEKLY_REVIEW_JOB",
309
+ "MONTHLY_STRATEGY_POOL_JOB",
310
+ "DEFAULT_QUANT_CRON_JOBS",
311
+ "build_quant_cron_jobs_from_env",
312
+ "register_quant_cron_jobs",
313
+ "NanobotNotInstalledForCron",
314
+ ]
@@ -0,0 +1,242 @@
1
+ # coding=utf-8
2
+ """Agent facade wrapping HKUDS nanobot 0.2.1 (Path A: direct upstream consumption).
3
+
4
+ Usage (backward-compatible v2.x signature):
5
+
6
+ from QuantNodes.agent import Agent
7
+ agent = Agent(workspace=".agent", config={...})
8
+ result = await agent.run("hello", session_id="default")
9
+
10
+ Under the hood, Agent constructs a ``Nanobot`` from
11
+ ``.agent/nanobot_config.json`` (generated from ``.env`` by
12
+ ``config_mapper.py``), injects all 15 quant tools into its ToolRegistry,
13
+ and exposes a thin compatibility layer for the streaming event protocol
14
+ that the API layer (api/services/agent_service.py) expects.
15
+
16
+ Reference: docs/13-Agent架构设计.md (v3.0.0) and docs/14-上游nanobot升级指南.md.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import asyncio
22
+ import json
23
+ import logging
24
+ import os
25
+ from pathlib import Path
26
+ from typing import Any, AsyncGenerator, Dict, List, Optional
27
+
28
+ # v3.0.0 Stage 5.3: nanobot-ai is now an optional dep. We delay the import
29
+ # until ``Agent.__init__`` is called so that ``from QuantNodes.agent import
30
+ # Agent`` works even when the [agent] extra is not installed (the import path
31
+ # returns a proxy that raises ``NanobotNotInstalled`` on actual use).
32
+ try:
33
+ from nanobot import Nanobot # type: ignore[import-not-found]
34
+ from nanobot.agent.tools.base import Tool # type: ignore[import-not-found]
35
+ from nanobot.agent.tools.registry import ToolRegistry # type: ignore[import-not-found]
36
+ _NANOBOT_OK = True
37
+ except ImportError: # pragma: no cover - exercised in nanobot-less envs
38
+ Nanobot = None # type: ignore[assignment,misc]
39
+ Tool = None # type: ignore[assignment,misc]
40
+ ToolRegistry = None # type: ignore[assignment,misc]
41
+ _NANOBOT_OK = False
42
+
43
+ from .config_mapper import build_nanobot_config, write_nanobot_config
44
+ from .tools import register_all_quant_tools
45
+
46
+ logger = logging.getLogger(__name__)
47
+
48
+
49
+ class Agent:
50
+ """Thin wrapper around HKUDS nanobot's ``Nanobot`` programmatic facade.
51
+
52
+ See nanobot_bridge.py module docstring for the full design rationale.
53
+ """
54
+
55
+ DEFAULT_WORKSPACE = ".agent"
56
+
57
+ def __init__(
58
+ self,
59
+ workspace: str = DEFAULT_WORKSPACE,
60
+ config: Optional[Dict[str, Any]] = None,
61
+ ):
62
+ if not _NANOBOT_OK:
63
+ from . import NanobotNotInstalled
64
+ raise NanobotNotInstalled("Agent")
65
+ config = config or {}
66
+ self.workspace = Path(workspace).expanduser().resolve()
67
+ self.workspace.mkdir(parents=True, exist_ok=True)
68
+
69
+ settings = build_nanobot_config(self.workspace, config)
70
+ self.config_path = write_nanobot_config(self.workspace, settings)
71
+
72
+ self._bot: Nanobot = Nanobot.from_config(self.config_path, workspace=self.workspace)
73
+ self._loop = self._bot._loop
74
+ self._hook_lock = asyncio.Lock()
75
+
76
+ quant_count = register_all_quant_tools(
77
+ self._loop.tools,
78
+ workspace=self.workspace,
79
+ llm_client=getattr(self._loop, 'provider', None),
80
+ model=getattr(self._loop, 'model', None),
81
+ )
82
+ logger.info(
83
+ "QuantNodes Agent ready (workspace=%s, quant_tools=%d, "
84
+ "upstream_tools=%d)",
85
+ self.workspace,
86
+ quant_count,
87
+ len(self._loop.tools._tools) - quant_count,
88
+ )
89
+
90
+ @property
91
+ def loop(self):
92
+ """Backward-compatible access to underlying AgentLoop."""
93
+ return self._loop
94
+
95
+ async def run(self, prompt: str, session_id: str = "default") -> str:
96
+ """Single-turn run returning the assistant's final text content."""
97
+ result = await self._bot.run(prompt, session_key=session_id)
98
+ return result.content or ""
99
+
100
+ async def chat(
101
+ self,
102
+ message: str,
103
+ session_id: str = "default",
104
+ model: Optional[str] = None,
105
+ max_tokens: Optional[int] = None,
106
+ mode: Optional[str] = None,
107
+ tools: Optional[List[str]] = None,
108
+ tool_choice: Optional[str] = None,
109
+ temperature: Optional[float] = None,
110
+ ) -> AsyncGenerator[Dict[str, Any], None]:
111
+ """Stream chat events using the v2.x event protocol.
112
+
113
+ Emits dicts with shape::
114
+
115
+ {"type": "token", "content": str}
116
+ {"type": "tool_call", "id": str, "name": str, "arguments": dict}
117
+ {"type": "tool_result", "id": str, "name": str, "content": Any, "success": bool}
118
+ {"type": "done", "content": str, "tools_used": list[str], "stop_reason": str}
119
+ {"type": "error", "content": str}
120
+
121
+ Args:
122
+ tools: 工具名列表 (None=全部, []=无工具)
123
+ tool_choice: "auto"/"none"/"required"
124
+ temperature: 采样温度(可选)
125
+ """
126
+ from nanobot.agent.hook import SDKCaptureHook
127
+
128
+ capture = SDKCaptureHook()
129
+ async with self._hook_lock:
130
+ prev = self._loop._extra_hooks or []
131
+ self._loop._extra_hooks = [capture, *prev]
132
+ try:
133
+ async for event in self._stream_via_loop(
134
+ message, session_id, model, max_tokens, mode,
135
+ tools=tools, tool_choice=tool_choice, temperature=temperature,
136
+ ):
137
+ yield event
138
+ finally:
139
+ async with self._hook_lock:
140
+ self._loop._extra_hooks = prev
141
+
142
+ def _filter_tools(self, tool_names: Optional[List[str]]) -> ToolRegistry:
143
+ """过滤 nanobot 工具集。
144
+
145
+ Args:
146
+ tool_names: 工具名列表 (None=全部, []=无工具)
147
+
148
+ Returns:
149
+ ToolRegistry (可能是子集)
150
+ """
151
+ if tool_names is None:
152
+ return self._loop.tools
153
+
154
+ all_names = set(self._loop.tools._tools.keys())
155
+ valid = [n for n in tool_names if n in all_names]
156
+ invalid = [n for n in tool_names if n not in all_names]
157
+ if invalid:
158
+ logger.warning("LLMGateway: unknown tools filtered: %s", invalid)
159
+
160
+ sub = ToolRegistry()
161
+ for name in valid:
162
+ sub.register(self._loop.tools._tools[name])
163
+ return sub
164
+
165
+ async def _stream_via_loop(
166
+ self,
167
+ message: str,
168
+ session_id: str,
169
+ model: Optional[str],
170
+ max_tokens: Optional[int],
171
+ mode: Optional[str],
172
+ tools: Optional[List[str]] = None,
173
+ tool_choice: Optional[str] = None,
174
+ temperature: Optional[float] = None,
175
+ ) -> AsyncGenerator[Dict[str, Any], None]:
176
+ """Drive the upstream AgentLoop and re-emit events in v2.x protocol."""
177
+ from nanobot.agent.runner import AgentRunSpec
178
+
179
+ filtered = self._filter_tools(tools)
180
+ spec = AgentRunSpec(
181
+ initial_messages=[{"role": "user", "content": message}],
182
+ tools=filtered,
183
+ model=model or getattr(self._loop, "model", None),
184
+ max_tokens=max_tokens or 16384,
185
+ max_iterations=getattr(self._loop, "max_iterations", 5),
186
+ max_tool_result_chars=getattr(self._loop, "max_tool_result_chars", 50000),
187
+ temperature=temperature,
188
+ )
189
+
190
+ tools_used: List[str] = []
191
+ final_content = ""
192
+ try:
193
+ async for event in self._loop.run_stream(spec):
194
+ etype = event.get("type")
195
+ if etype == "token":
196
+ yield {"type": "token", "content": event.get("content", "")}
197
+ elif etype == "tool_call":
198
+ name = event.get("name") or event.get("tool", "")
199
+ if name:
200
+ tools_used.append(name)
201
+ yield {
202
+ "type": "tool_call",
203
+ "id": event.get("id", ""),
204
+ "name": name,
205
+ "arguments": event.get("arguments", {}),
206
+ }
207
+ elif etype == "tool_result":
208
+ yield {
209
+ "type": "tool_result",
210
+ "id": event.get("id", ""),
211
+ "name": event.get("name", ""),
212
+ "content": event.get("content"),
213
+ "success": event.get("success", True),
214
+ }
215
+ elif etype == "done":
216
+ final_content = event.get("content", "") or final_content
217
+ yield {
218
+ "type": "done",
219
+ "content": final_content,
220
+ "tools_used": list(dict.fromkeys(tools_used)),
221
+ "stop_reason": event.get("stop_reason", "stop"),
222
+ }
223
+ elif etype == "error":
224
+ yield {"type": "error", "content": event.get("content", "")}
225
+ elif etype == "message":
226
+ delta = event.get("content")
227
+ if delta:
228
+ final_content += delta
229
+ except AttributeError:
230
+ result = await self._loop.process_direct(message, session_key=session_id)
231
+ yield {
232
+ "type": "done",
233
+ "content": getattr(result, "content", "") or "",
234
+ "tools_used": tools_used,
235
+ "stop_reason": getattr(result, "stop_reason", "stop") or "stop",
236
+ }
237
+ except Exception as exc:
238
+ logger.exception("Agent chat failed")
239
+ yield {"type": "error", "content": str(exc)}
240
+
241
+
242
+ __all__ = ["Agent"]
@@ -0,0 +1,30 @@
1
+ # coding=utf-8
2
+ """
3
+ 权限系统模块
4
+ """
5
+
6
+ from .models import (
7
+ Action,
8
+ PermissionRule,
9
+ PermissionRequest,
10
+ PermissionReply,
11
+ Ruleset,
12
+ PermissionDeniedError,
13
+ PermissionRejectedError,
14
+ )
15
+ from .evaluate import evaluate
16
+ from .service import PermissionService
17
+ from .defaults import create_default_ruleset
18
+
19
+ __all__ = [
20
+ "Action",
21
+ "PermissionRule",
22
+ "PermissionRequest",
23
+ "PermissionReply",
24
+ "Ruleset",
25
+ "PermissionDeniedError",
26
+ "PermissionRejectedError",
27
+ "evaluate",
28
+ "PermissionService",
29
+ "create_default_ruleset",
30
+ ]
@@ -0,0 +1,36 @@
1
+ # coding=utf-8
2
+ """
3
+ 默认权限规则集
4
+ """
5
+
6
+ from pathlib import Path
7
+ from .models import PermissionRule, Action
8
+
9
+
10
+ def create_default_ruleset(project_root: str | Path) -> list[PermissionRule]:
11
+ """创建默认权限规则集
12
+
13
+ 安全原则:
14
+ 1. 默认询问(安全默认)
15
+ 2. 项目内文件读取允许
16
+ 3. 敏感文件(.env)需要审批
17
+ 4. 外部目录访问需要审批
18
+ 5. shell 命令需要审批
19
+ """
20
+ project_root = Path(project_root)
21
+
22
+ return [
23
+ PermissionRule("*", "*", Action.ASK),
24
+ PermissionRule("read", "*", Action.ALLOW),
25
+ PermissionRule("read", "*.env", Action.ASK),
26
+ PermissionRule("read", "*.env.*", Action.ASK),
27
+ PermissionRule("read", ".env", Action.ASK),
28
+ PermissionRule("read", ".env.*", Action.ASK),
29
+ PermissionRule("read", "*.env.example", Action.ALLOW),
30
+ PermissionRule("edit", "*", Action.ALLOW),
31
+ PermissionRule("bash", "*", Action.ASK),
32
+ PermissionRule("external_directory", "*", Action.ASK),
33
+ PermissionRule("external_directory", "/tmp/*", Action.ALLOW),
34
+ PermissionRule("webfetch", "*", Action.ALLOW),
35
+ PermissionRule("websearch", "*", Action.ALLOW),
36
+ ]
@@ -0,0 +1,41 @@
1
+ # coding=utf-8
2
+ """
3
+ 权限规则评估引擎
4
+ """
5
+
6
+ from .models import PermissionRule, Action, Ruleset
7
+
8
+
9
+ def evaluate(
10
+ permission: str,
11
+ target: str,
12
+ *rulesets: Ruleset,
13
+ ) -> PermissionRule:
14
+ """评估权限规则
15
+
16
+ 规则评估逻辑:
17
+ 1. 将所有 ruleset 扁平化
18
+ 2. 从后向前查找第一个匹配的规则(后定义的规则优先)
19
+ 3. 如果没有匹配规则,默认返回 ask(安全默认)
20
+
21
+ Args:
22
+ permission: 权限类别(如 "bash", "edit")
23
+ target: 目标模式(如 "git commit", "/path/to/file.py")
24
+ *rulesets: 规则集(按优先级从低到高排列)
25
+
26
+ Returns:
27
+ 匹配的规则(包含 action)
28
+ """
29
+ all_rules = []
30
+ for rs in rulesets:
31
+ all_rules.extend(rs)
32
+
33
+ for rule in reversed(all_rules):
34
+ if rule.matches(permission, target):
35
+ return rule
36
+
37
+ return PermissionRule(
38
+ permission=permission,
39
+ pattern="*",
40
+ action=Action.ASK,
41
+ )