langchain-agentx-python 0.1__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 (354) hide show
  1. langchain_agentx/__init__.py +46 -0
  2. langchain_agentx/command/__init__.py +28 -0
  3. langchain_agentx/command/builtin/__init__.py +25 -0
  4. langchain_agentx/command/builtin/clear.py +33 -0
  5. langchain_agentx/command/builtin/compact.py +33 -0
  6. langchain_agentx/command/builtin/memory.py +37 -0
  7. langchain_agentx/command/builtin/reload_plugins.py +42 -0
  8. langchain_agentx/command/context.py +30 -0
  9. langchain_agentx/command/dispatcher.py +183 -0
  10. langchain_agentx/command/registry.py +110 -0
  11. langchain_agentx/command/result.py +25 -0
  12. langchain_agentx/command/types.py +41 -0
  13. langchain_agentx/config/__init__.py +14 -0
  14. langchain_agentx/loop/__init__.py +47 -0
  15. langchain_agentx/loop/config/__init__.py +20 -0
  16. langchain_agentx/loop/config/agent_config.py +66 -0
  17. langchain_agentx/loop/config/agent_loop_config.py +72 -0
  18. langchain_agentx/loop/config/model_context_resolver.py +105 -0
  19. langchain_agentx/loop/config/runtime_settings.py +50 -0
  20. langchain_agentx/loop/config/token_estimator.py +133 -0
  21. langchain_agentx/loop/context/__init__.py +66 -0
  22. langchain_agentx/loop/context/blocking_guard.py +97 -0
  23. langchain_agentx/loop/context/compaction_service.py +60 -0
  24. langchain_agentx/loop/context/message_utils.py +56 -0
  25. langchain_agentx/loop/context/pipeline.py +127 -0
  26. langchain_agentx/loop/context/settings.py +103 -0
  27. langchain_agentx/loop/context/stages/__init__.py +29 -0
  28. langchain_agentx/loop/context/stages/autocompact.py +140 -0
  29. langchain_agentx/loop/context/stages/base.py +32 -0
  30. langchain_agentx/loop/context/stages/collapse.py +76 -0
  31. langchain_agentx/loop/context/stages/microcompact.py +76 -0
  32. langchain_agentx/loop/context/stages/noop.py +33 -0
  33. langchain_agentx/loop/context/stages/snip.py +71 -0
  34. langchain_agentx/loop/context/stages/tool_result_budget.py +69 -0
  35. langchain_agentx/loop/context/types.py +79 -0
  36. langchain_agentx/loop/exit/__init__.py +1 -0
  37. langchain_agentx/loop/exit/exit_logic.py +320 -0
  38. langchain_agentx/loop/exit/reason_codes.py +39 -0
  39. langchain_agentx/loop/graph/__init__.py +5 -0
  40. langchain_agentx/loop/graph/builtin_loop_control.py +197 -0
  41. langchain_agentx/loop/graph/factory.py +1409 -0
  42. langchain_agentx/loop/graph/graph_edges.py +820 -0
  43. langchain_agentx/loop/hook/__init__.py +48 -0
  44. langchain_agentx/loop/hook/async_hook_runner.py +62 -0
  45. langchain_agentx/loop/hook/config.py +280 -0
  46. langchain_agentx/loop/hook/engine.py +321 -0
  47. langchain_agentx/loop/hook/executors/__init__.py +9 -0
  48. langchain_agentx/loop/hook/executors/agent.py +107 -0
  49. langchain_agentx/loop/hook/executors/command.py +230 -0
  50. langchain_agentx/loop/hook/executors/http.py +114 -0
  51. langchain_agentx/loop/hook/executors/prompt.py +92 -0
  52. langchain_agentx/loop/hook/graph_wiring.py +134 -0
  53. langchain_agentx/loop/hook/registry.py +262 -0
  54. langchain_agentx/loop/hook/trust.py +43 -0
  55. langchain_agentx/loop/hook/types.py +110 -0
  56. langchain_agentx/loop/injection/__init__.py +13 -0
  57. langchain_agentx/loop/injection/dedup.py +74 -0
  58. langchain_agentx/loop/loop_abort.py +36 -0
  59. langchain_agentx/loop/model/__init__.py +1 -0
  60. langchain_agentx/loop/model/model_node.py +648 -0
  61. langchain_agentx/loop/model/model_nodes.py +661 -0
  62. langchain_agentx/loop/model/orphan_tool_results.py +38 -0
  63. langchain_agentx/loop/model/retrier.py +307 -0
  64. langchain_agentx/loop/model/retry_bridge.py +58 -0
  65. langchain_agentx/loop/model/retry_events.py +35 -0
  66. langchain_agentx/loop/model/retry_policy.py +56 -0
  67. langchain_agentx/loop/model/schema_and_format.py +153 -0
  68. langchain_agentx/loop/model/tool_and_model_binding.py +227 -0
  69. langchain_agentx/loop/model/tool_call_degradation_corrector.py +443 -0
  70. langchain_agentx/loop/model/tool_transcript_guard.py +225 -0
  71. langchain_agentx/loop/prompt/__init__.py +95 -0
  72. langchain_agentx/loop/prompt/builder.py +61 -0
  73. langchain_agentx/loop/prompt/builtin.py +218 -0
  74. langchain_agentx/loop/prompt/compact.py +408 -0
  75. langchain_agentx/loop/prompt/sections.py +120 -0
  76. langchain_agentx/loop/runtime/__init__.py +19 -0
  77. langchain_agentx/loop/runtime/context.py +34 -0
  78. langchain_agentx/loop/runtime/context_factory.py +107 -0
  79. langchain_agentx/loop/runtime/subagent_execution_paths.py +68 -0
  80. langchain_agentx/loop/subagent/__init__.py +53 -0
  81. langchain_agentx/loop/subagent/async_runner.py +215 -0
  82. langchain_agentx/loop/subagent/context.py +209 -0
  83. langchain_agentx/loop/subagent/fork_worktree_notice.py +25 -0
  84. langchain_agentx/loop/subagent/graph.py +72 -0
  85. langchain_agentx/loop/subagent/orchestrator.py +391 -0
  86. langchain_agentx/loop/subagent/progress.py +30 -0
  87. langchain_agentx/loop/subagent/prompt.py +52 -0
  88. langchain_agentx/loop/subagent/runner.py +504 -0
  89. langchain_agentx/loop/subagent/transcript.py +172 -0
  90. langchain_agentx/memory/__init__.py +2 -0
  91. langchain_agentx/memory/instruction/__init__.py +12 -0
  92. langchain_agentx/memory/instruction/loader.py +325 -0
  93. langchain_agentx/memory/instruction/resolver.py +24 -0
  94. langchain_agentx/memory/instruction/runtime.py +83 -0
  95. langchain_agentx/memory/instruction/sections.py +83 -0
  96. langchain_agentx/memory/instruction/types.py +59 -0
  97. langchain_agentx/memory/memdir/__init__.py +77 -0
  98. langchain_agentx/memory/memdir/age.py +36 -0
  99. langchain_agentx/memory/memdir/agent_memory.py +380 -0
  100. langchain_agentx/memory/memdir/extractor.py +309 -0
  101. langchain_agentx/memory/memdir/loader.py +187 -0
  102. langchain_agentx/memory/memdir/paths.py +63 -0
  103. langchain_agentx/memory/memdir/recall.py +45 -0
  104. langchain_agentx/memory/memdir/runtime.py +43 -0
  105. langchain_agentx/memory/memdir/scan.py +135 -0
  106. langchain_agentx/memory/memdir/types.py +104 -0
  107. langchain_agentx/memory/session/__init__.py +76 -0
  108. langchain_agentx/memory/session/compact_bridge.py +208 -0
  109. langchain_agentx/memory/session/prompts.py +172 -0
  110. langchain_agentx/memory/session/session_memory.py +282 -0
  111. langchain_agentx/observability/__init__.py +67 -0
  112. langchain_agentx/observability/evaluation/__init__.py +17 -0
  113. langchain_agentx/observability/evaluation/checkers/__init__.py +18 -0
  114. langchain_agentx/observability/evaluation/checkers/base.py +34 -0
  115. langchain_agentx/observability/evaluation/checkers/compaction.py +38 -0
  116. langchain_agentx/observability/evaluation/checkers/degradation.py +50 -0
  117. langchain_agentx/observability/evaluation/checkers/exit_quality.py +42 -0
  118. langchain_agentx/observability/evaluation/checkers/session_memory.py +45 -0
  119. langchain_agentx/observability/evaluation/checkers/tool_behavior.py +53 -0
  120. langchain_agentx/observability/evaluation/retention_scheduler.py +67 -0
  121. langchain_agentx/observability/evaluation/service.py +102 -0
  122. langchain_agentx/observability/evaluation/state.py +32 -0
  123. langchain_agentx/observability/evaluation/store.py +258 -0
  124. langchain_agentx/observability/events/__init__.py +15 -0
  125. langchain_agentx/observability/events/langchain_agentx_event_adapter.py +832 -0
  126. langchain_agentx/observability/logging/__init__.py +15 -0
  127. langchain_agentx/observability/logging/debug_burst.py +95 -0
  128. langchain_agentx/observability/logging/logging_config.py +178 -0
  129. langchain_agentx/observability/logging/logging_contract.py +65 -0
  130. langchain_agentx/observability/replay/__init__.py +35 -0
  131. langchain_agentx/observability/replay/cli.py +91 -0
  132. langchain_agentx/observability/replay/service.py +83 -0
  133. langchain_agentx/observability/replay/store.py +278 -0
  134. langchain_agentx/observability/replay/ui.py +47 -0
  135. langchain_agentx/observability/trace/__init__.py +25 -0
  136. langchain_agentx/observability/trace/collector.py +560 -0
  137. langchain_agentx/observability/trace/event_emitter.py +183 -0
  138. langchain_agentx/observability/trace/hook_event_emitter.py +49 -0
  139. langchain_agentx/observability/trace/models.py +144 -0
  140. langchain_agentx/observability/trace/sqlite_store.py +873 -0
  141. langchain_agentx/observability/trace/trace_callback.py +295 -0
  142. langchain_agentx/observability/trace/trace_lifecycle_collector.py +114 -0
  143. langchain_agentx/plugin/__init__.py +26 -0
  144. langchain_agentx/plugin/builtin.py +53 -0
  145. langchain_agentx/plugin/config.py +113 -0
  146. langchain_agentx/plugin/loader.py +386 -0
  147. langchain_agentx/plugin/manifest.py +154 -0
  148. langchain_agentx/plugin/registries.py +211 -0
  149. langchain_agentx/plugin/types.py +142 -0
  150. langchain_agentx/provider/__init__.py +27 -0
  151. langchain_agentx/provider/anthropic.py +121 -0
  152. langchain_agentx/provider/compatible_chat_openai.py +86 -0
  153. langchain_agentx/provider/env.py +45 -0
  154. langchain_agentx/provider/model_profile.py +156 -0
  155. langchain_agentx/provider/openai.py +89 -0
  156. langchain_agentx/session/__init__.py +17 -0
  157. langchain_agentx/session/agent_session.py +320 -0
  158. langchain_agentx/session/conversation_factory.py +87 -0
  159. langchain_agentx/session/conversation_recovery.py +156 -0
  160. langchain_agentx/session/conversation_session.py +198 -0
  161. langchain_agentx/session/factory.py +143 -0
  162. langchain_agentx/session/protocol.py +25 -0
  163. langchain_agentx/task_runtime/__init__.py +113 -0
  164. langchain_agentx/task_runtime/core/__init__.py +51 -0
  165. langchain_agentx/task_runtime/core/ids.py +33 -0
  166. langchain_agentx/task_runtime/core/interfaces.py +115 -0
  167. langchain_agentx/task_runtime/core/notification_priority.py +19 -0
  168. langchain_agentx/task_runtime/core/types.py +136 -0
  169. langchain_agentx/task_runtime/integrations/__init__.py +33 -0
  170. langchain_agentx/task_runtime/integrations/loop_adapter.py +91 -0
  171. langchain_agentx/task_runtime/integrations/loop_integration.py +61 -0
  172. langchain_agentx/task_runtime/integrations/prefetch_providers.py +108 -0
  173. langchain_agentx/task_runtime/integrations/provider_factory.py +103 -0
  174. langchain_agentx/task_runtime/integrations/queued_command_provider.py +184 -0
  175. langchain_agentx/task_runtime/integrations/sqlite_queued_command_provider.py +338 -0
  176. langchain_agentx/task_runtime/integrations/tool_use_summary_provider.py +254 -0
  177. langchain_agentx/task_runtime/orchestrator/__init__.py +5 -0
  178. langchain_agentx/task_runtime/orchestrator/runtime.py +386 -0
  179. langchain_agentx/task_runtime/output/__init__.py +5 -0
  180. langchain_agentx/task_runtime/output/sink.py +64 -0
  181. langchain_agentx/task_runtime/policy/__init__.py +11 -0
  182. langchain_agentx/task_runtime/policy/withhold_visibility.py +32 -0
  183. langchain_agentx/task_runtime/queue/__init__.py +5 -0
  184. langchain_agentx/task_runtime/queue/in_memory.py +55 -0
  185. langchain_agentx/task_runtime/skill_prefetch/__init__.py +4 -0
  186. langchain_agentx/task_runtime/skill_prefetch/attachments.py +46 -0
  187. langchain_agentx/task_runtime/skill_prefetch/models.py +37 -0
  188. langchain_agentx/task_runtime/skill_prefetch/provider.py +344 -0
  189. langchain_agentx/task_runtime/store/__init__.py +6 -0
  190. langchain_agentx/task_runtime/store/in_memory.py +81 -0
  191. langchain_agentx/task_runtime/store/sqlite_store.py +281 -0
  192. langchain_agentx/task_runtime/tasks/__init__.py +76 -0
  193. langchain_agentx/task_runtime/tasks/ai_analysis/__init__.py +15 -0
  194. langchain_agentx/task_runtime/tasks/ai_analysis/base.py +41 -0
  195. langchain_agentx/task_runtime/tasks/ai_analysis/evaluation.py +67 -0
  196. langchain_agentx/task_runtime/tasks/ai_analysis/registry.py +36 -0
  197. langchain_agentx/task_runtime/tasks/ai_analysis/scheduler.py +70 -0
  198. langchain_agentx/task_runtime/tasks/base/__init__.py +6 -0
  199. langchain_agentx/task_runtime/tasks/base/contracts.py +24 -0
  200. langchain_agentx/task_runtime/tasks/custom/__init__.py +7 -0
  201. langchain_agentx/task_runtime/tasks/custom/executor.py +60 -0
  202. langchain_agentx/task_runtime/tasks/custom/notification.py +7 -0
  203. langchain_agentx/task_runtime/tasks/custom/semantics.py +13 -0
  204. langchain_agentx/task_runtime/tasks/custom/spec.py +33 -0
  205. langchain_agentx/task_runtime/tasks/dream_task/__init__.py +15 -0
  206. langchain_agentx/task_runtime/tasks/dream_task/executor.py +61 -0
  207. langchain_agentx/task_runtime/tasks/dream_task/notification.py +19 -0
  208. langchain_agentx/task_runtime/tasks/dream_task/semantics.py +13 -0
  209. langchain_agentx/task_runtime/tasks/dream_task/spec.py +35 -0
  210. langchain_agentx/task_runtime/tasks/dream_task/state.py +17 -0
  211. langchain_agentx/task_runtime/tasks/in_process_teammate/__init__.py +12 -0
  212. langchain_agentx/task_runtime/tasks/in_process_teammate/executor.py +36 -0
  213. langchain_agentx/task_runtime/tasks/in_process_teammate/notification.py +25 -0
  214. langchain_agentx/task_runtime/tasks/in_process_teammate/semantics.py +13 -0
  215. langchain_agentx/task_runtime/tasks/in_process_teammate/spec.py +63 -0
  216. langchain_agentx/task_runtime/tasks/local_agent/__init__.py +14 -0
  217. langchain_agentx/task_runtime/tasks/local_agent/executor.py +33 -0
  218. langchain_agentx/task_runtime/tasks/local_agent/notification.py +21 -0
  219. langchain_agentx/task_runtime/tasks/local_agent/runner.py +43 -0
  220. langchain_agentx/task_runtime/tasks/local_agent/semantics.py +13 -0
  221. langchain_agentx/task_runtime/tasks/local_agent/spec.py +31 -0
  222. langchain_agentx/task_runtime/tasks/local_bash/__init__.py +13 -0
  223. langchain_agentx/task_runtime/tasks/local_bash/executor.py +95 -0
  224. langchain_agentx/task_runtime/tasks/local_bash/notification.py +22 -0
  225. langchain_agentx/task_runtime/tasks/local_bash/semantics.py +13 -0
  226. langchain_agentx/task_runtime/tasks/local_bash/spec.py +55 -0
  227. langchain_agentx/task_runtime/tasks/remote_agent/__init__.py +19 -0
  228. langchain_agentx/task_runtime/tasks/remote_agent/backend.py +76 -0
  229. langchain_agentx/task_runtime/tasks/remote_agent/executor.py +37 -0
  230. langchain_agentx/task_runtime/tasks/remote_agent/notification.py +22 -0
  231. langchain_agentx/task_runtime/tasks/remote_agent/semantics.py +13 -0
  232. langchain_agentx/task_runtime/tasks/remote_agent/spec.py +34 -0
  233. langchain_agentx/task_runtime/tasks/trace_cleanup/__init__.py +19 -0
  234. langchain_agentx/task_runtime/tasks/trace_cleanup/bootstrap.py +95 -0
  235. langchain_agentx/task_runtime/tasks/trace_cleanup/executor.py +66 -0
  236. langchain_agentx/task_runtime/tasks/trace_cleanup/scheduler.py +169 -0
  237. langchain_agentx/tool_runtime/__init__.py +90 -0
  238. langchain_agentx/tool_runtime/adapter.py +365 -0
  239. langchain_agentx/tool_runtime/base.py +319 -0
  240. langchain_agentx/tool_runtime/errors.py +190 -0
  241. langchain_agentx/tool_runtime/identical_call_cache.py +110 -0
  242. langchain_agentx/tool_runtime/loader.py +195 -0
  243. langchain_agentx/tool_runtime/models.py +260 -0
  244. langchain_agentx/tool_runtime/permission_context.py +78 -0
  245. langchain_agentx/tool_runtime/pipeline.py +621 -0
  246. langchain_agentx/tool_runtime/policy.py +447 -0
  247. langchain_agentx/tool_runtime/registry.py +81 -0
  248. langchain_agentx/tool_runtime/resolvers/__init__.py +27 -0
  249. langchain_agentx/tool_runtime/resolvers/agent_session.py +125 -0
  250. langchain_agentx/tool_runtime/resolvers/background.py +32 -0
  251. langchain_agentx/tool_runtime/resolvers/base.py +20 -0
  252. langchain_agentx/tool_runtime/resolvers/conversation.py +22 -0
  253. langchain_agentx/tool_runtime/resolvers/workflow.py +73 -0
  254. langchain_agentx/tool_runtime/session_store.py +132 -0
  255. langchain_agentx/tool_runtime/smoke_test_runtime.py +294 -0
  256. langchain_agentx/tool_runtime/state_bridge.py +164 -0
  257. langchain_agentx/tools/__init__.py +26 -0
  258. langchain_agentx/tools/agent/__init__.py +9 -0
  259. langchain_agentx/tools/agent/backend.py +53 -0
  260. langchain_agentx/tools/agent/built_in/__init__.py +19 -0
  261. langchain_agentx/tools/agent/built_in/agentx_guide.py +65 -0
  262. langchain_agentx/tools/agent/built_in/explore.py +80 -0
  263. langchain_agentx/tools/agent/built_in/general.py +57 -0
  264. langchain_agentx/tools/agent/built_in/plan.py +89 -0
  265. langchain_agentx/tools/agent/built_in/statusline_setup.py +64 -0
  266. langchain_agentx/tools/agent/built_in/verification.py +120 -0
  267. langchain_agentx/tools/agent/builtin_subagent_loader.py +89 -0
  268. langchain_agentx/tools/agent/cwd_resolution.py +119 -0
  269. langchain_agentx/tools/agent/limits.py +26 -0
  270. langchain_agentx/tools/agent/loader.py +270 -0
  271. langchain_agentx/tools/agent/models.py +85 -0
  272. langchain_agentx/tools/agent/prompt.py +120 -0
  273. langchain_agentx/tools/agent/registry/__init__.py +18 -0
  274. langchain_agentx/tools/agent/registry/config.py +29 -0
  275. langchain_agentx/tools/agent/registry/registry.py +47 -0
  276. langchain_agentx/tools/agent/scope.py +137 -0
  277. langchain_agentx/tools/agent/tool.py +256 -0
  278. langchain_agentx/tools/bash/__init__.py +9 -0
  279. langchain_agentx/tools/bash/ast_security.py +571 -0
  280. langchain_agentx/tools/bash/backend.py +1447 -0
  281. langchain_agentx/tools/bash/bash_hardening.py +734 -0
  282. langchain_agentx/tools/bash/bash_runtime_contract.py +41 -0
  283. langchain_agentx/tools/bash/cwd_reporter.py +95 -0
  284. langchain_agentx/tools/bash/limits.py +71 -0
  285. langchain_agentx/tools/bash/mode_validation.py +282 -0
  286. langchain_agentx/tools/bash/models.py +131 -0
  287. langchain_agentx/tools/bash/observability.py +148 -0
  288. langchain_agentx/tools/bash/output_utils.py +200 -0
  289. langchain_agentx/tools/bash/path_security.py +2429 -0
  290. langchain_agentx/tools/bash/prompt.py +68 -0
  291. langchain_agentx/tools/bash/read_only_validation.py +589 -0
  292. langchain_agentx/tools/bash/result_presenter.py +324 -0
  293. langchain_agentx/tools/bash/sandbox_decision.py +133 -0
  294. langchain_agentx/tools/bash/security.py +311 -0
  295. langchain_agentx/tools/bash/sed_edit_parser.py +243 -0
  296. langchain_agentx/tools/bash/sed_validation.py +163 -0
  297. langchain_agentx/tools/bash/semantics.py +111 -0
  298. langchain_agentx/tools/bash/session_manager.py +205 -0
  299. langchain_agentx/tools/bash/session_runtime.py +290 -0
  300. langchain_agentx/tools/bash/shell_locator.py +191 -0
  301. langchain_agentx/tools/bash/task_runtime.py +91 -0
  302. langchain_agentx/tools/bash/tool.py +939 -0
  303. langchain_agentx/tools/bash/windows_shell_quoting.py +45 -0
  304. langchain_agentx/tools/glob/__init__.py +9 -0
  305. langchain_agentx/tools/glob/models.py +57 -0
  306. langchain_agentx/tools/glob/pagination.py +30 -0
  307. langchain_agentx/tools/glob/prompt.py +24 -0
  308. langchain_agentx/tools/glob/rg_list_backend.py +139 -0
  309. langchain_agentx/tools/glob/rg_pattern.py +44 -0
  310. langchain_agentx/tools/glob/tool.py +327 -0
  311. langchain_agentx/tools/grep/__init__.py +7 -0
  312. langchain_agentx/tools/grep/backend.py +375 -0
  313. langchain_agentx/tools/grep/models.py +127 -0
  314. langchain_agentx/tools/grep/prompt.py +30 -0
  315. langchain_agentx/tools/grep/rg_subprocess_controller.py +114 -0
  316. langchain_agentx/tools/grep/tool.py +475 -0
  317. langchain_agentx/tools/read/__init__.py +9 -0
  318. langchain_agentx/tools/read/backend.py +415 -0
  319. langchain_agentx/tools/read/limits.py +67 -0
  320. langchain_agentx/tools/read/models.py +156 -0
  321. langchain_agentx/tools/read/prompt.py +73 -0
  322. langchain_agentx/tools/read/tool.py +494 -0
  323. langchain_agentx/tools/ripgrep_plugin_exclusions.py +137 -0
  324. langchain_agentx/tools/skill/__init__.py +4 -0
  325. langchain_agentx/tools/skill/argument_substitution.py +80 -0
  326. langchain_agentx/tools/skill/loader.py +196 -0
  327. langchain_agentx/tools/skill/models.py +88 -0
  328. langchain_agentx/tools/skill/policy.py +80 -0
  329. langchain_agentx/tools/skill/prompt.py +35 -0
  330. langchain_agentx/tools/skill/tool.py +222 -0
  331. langchain_agentx/utils/__init__.py +0 -0
  332. langchain_agentx/utils/cwd.py +124 -0
  333. langchain_agentx/utils/host_platform.py +112 -0
  334. langchain_agentx/utils/path_hierarchy.py +48 -0
  335. langchain_agentx/utils/path_user_input.py +66 -0
  336. langchain_agentx/utils/rg_executable.py +18 -0
  337. langchain_agentx/utils/subprocess_text.py +101 -0
  338. langchain_agentx/utils/temp_paths.py +77 -0
  339. langchain_agentx/utils/unc_path.py +25 -0
  340. langchain_agentx/utils/win_reserved_paths.py +51 -0
  341. langchain_agentx/workflow/__init__.py +7 -0
  342. langchain_agentx/workflow/base.py +97 -0
  343. langchain_agentx/workflow/batch.py +55 -0
  344. langchain_agentx/workflow/dag.py +54 -0
  345. langchain_agentx/workspace/__init__.py +13 -0
  346. langchain_agentx/workspace/config.py +140 -0
  347. langchain_agentx/workspace/path_key_normalizer.py +30 -0
  348. langchain_agentx/workspace/resolver.py +74 -0
  349. langchain_agentx/workspace/validators.py +41 -0
  350. langchain_agentx_python-0.1.dist-info/LICENSE +201 -0
  351. langchain_agentx_python-0.1.dist-info/METADATA +513 -0
  352. langchain_agentx_python-0.1.dist-info/RECORD +354 -0
  353. langchain_agentx_python-0.1.dist-info/WHEEL +5 -0
  354. langchain_agentx_python-0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,661 @@
1
+ """
2
+ LangChain AgentX agent 的 model 节点实现(向后兼容层)。
3
+
4
+ 本模块当前职责:
5
+ - `make_model_nodes()` — 兼容接口,内部构造 ModelNode(Runnable) 并返回 (invoke, ainvoke)
6
+ - `_ModelNodeBuilder` — 旧构建器(已私有化),新增功能请直接在 ModelNode 上扩展
7
+ - 辅助函数:`_inject_model_into_agent_tool_calls`、`_messages_for_model_invoke`、
8
+ `_model_response_if_context_token_blocked`(与 model_node.py 共享)
9
+
10
+ 核心实现在 model_node.py → ModelNode(Runnable),本模块仅为向后兼容的薄封装。
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import copy
16
+ import logging
17
+ from typing import Any, Callable, Dict, List, Tuple
18
+
19
+ from langchain.agents.middleware.types import AgentState, ContextT
20
+ from langchain.agents.structured_output import ResponseFormat
21
+ from langchain_core.messages import AIMessage
22
+ from langchain_core.runnables.config import RunnableConfig
23
+ from langgraph.types import Command
24
+ from langgraph.runtime import Runtime
25
+
26
+ from .model_node import ModelNode
27
+ from ..context.settings import CompactionSettings, get_active_compaction_settings
28
+ from ..exit.exit_logic import (
29
+ MAX_OUTPUT_TOKENS_RECOVERY_LIMIT,
30
+ RECOVERABLE_TERMINAL_REASONS,
31
+ determine_should_end,
32
+ extract_finish_reason,
33
+ )
34
+ from ..exit.reason_codes import (
35
+ TERMINAL_REASON_BLOCKING_LIMIT,
36
+ TERMINAL_REASON_MAX_TOKENS,
37
+ TRANSITION_WITHHOLD_RECOVERY_EXHAUSTED,
38
+ TRANSITION_WITHHOLD_RECOVERY_RETRY,
39
+ )
40
+ from .retry_bridge import build_retry_event_commands
41
+ from .retrier import ModelCallRetrier, RetryContext
42
+ from .tool_and_model_binding import get_bound_model, handle_model_output
43
+ from .tool_transcript_guard import repair_messages_for_llm_invoke
44
+
45
+
46
+ def _synthetic_api_error_model_response(exc: Exception, name: str | None) -> Any:
47
+ """invoke/ainvoke 最终失败时合成 AIMessage,供 loop_controller CC §5.7 优先级 4 识别。"""
48
+ from langchain.agents.middleware.types import ModelResponse as _ModelResponse
49
+
50
+ meta = {
51
+ "finish_reason": "error",
52
+ "langchain_agentx_api_error": True,
53
+ "error": {
54
+ "type": type(exc).__name__,
55
+ "message": str(exc),
56
+ },
57
+ }
58
+ ai = AIMessage(content=f"[LLM request failed] {exc}", response_metadata=meta)
59
+ if name:
60
+ ai.name = name
61
+ return _ModelResponse(result=[ai], structured_response=None)
62
+
63
+
64
+ logger = logging.getLogger(__name__)
65
+
66
+
67
+ def _inject_model_into_agent_tool_calls(messages: list[Any], model: Any) -> None:
68
+ """在模型输出阶段为 Agent 工具调用补齐 model/tool_call_id 入参。"""
69
+ if not isinstance(model, str):
70
+ return
71
+ for msg in messages:
72
+ tool_calls = getattr(msg, "tool_calls", None)
73
+ if not isinstance(tool_calls, list):
74
+ continue
75
+ for tc in tool_calls:
76
+ if not isinstance(tc, dict) or tc.get("name") != "Agent":
77
+ continue
78
+ args = tc.get("args")
79
+ if not isinstance(args, dict):
80
+ continue
81
+ if not args.get("model"):
82
+ args["model"] = model
83
+ # 透传 LLM 原生 tool_call.id,供 Runtime 层建立 parent_tool_call_id 关联。
84
+ call_id = tc.get("id")
85
+ if isinstance(call_id, str) and call_id and not args.get("tool_call_id"):
86
+ args["tool_call_id"] = call_id
87
+
88
+
89
+ def _messages_for_model_invoke(req: Any) -> list[Any]:
90
+ """组装本次 LLM 调用使用的消息列表;可选在 invoke 前做 tool 管线修复(见 `tool_transcript_guard`)。"""
91
+ messages = list(req.messages or [])
92
+ s = get_active_compaction_settings()
93
+ if getattr(s, "enable_tool_transcript_repair_before_llm", True):
94
+ messages, stats = repair_messages_for_llm_invoke(messages)
95
+ if stats.get("repaired"):
96
+ logger.debug("tool_transcript_guard applied | %s", stats)
97
+ if req.system_message:
98
+ return [req.system_message, *messages]
99
+ return messages
100
+
101
+
102
+ def _model_response_if_context_token_blocked(
103
+ *,
104
+ state_messages: List[Any],
105
+ name: str | None,
106
+ initial_response_format: ResponseFormat[Any] | None,
107
+ structured_output_tools: Dict[str, Any],
108
+ compaction_settings: CompactionSettings | None,
109
+ ) -> Any | None:
110
+ """若主上下文启发式已超过 `CompactionSettings` 顶线,返回合成 `ModelResponse`,否则 None。"""
111
+ from langchain_agentx.loop.context.blocking_guard import CompactionBlockingGuard
112
+ from langchain_agentx.loop.context.types import ContextCompactionContext
113
+ from langchain.agents.middleware.types import ModelResponse as _ModelResponse
114
+
115
+ s = compaction_settings or get_active_compaction_settings()
116
+ if not s.enable_main_model_context_token_block:
117
+ return None
118
+ guard = CompactionBlockingGuard(settings=s)
119
+ est = guard.estimate_messages_tokens(state_messages)
120
+ if not guard.should_block(ContextCompactionContext(), estimated_tokens=est):
121
+ return None
122
+ tw = guard.calculate_token_warning_state(state_messages)
123
+ blocked_ai = AIMessage(
124
+ content=(
125
+ "[Stopped before LLM call: estimated conversation tokens "
126
+ f"({tw.estimated_tokens}) reached or exceeded the configured "
127
+ f"context ceiling ({tw.block_main_model_threshold_tokens}).]"
128
+ ),
129
+ response_metadata={
130
+ "finish_reason": "stop",
131
+ "langchain_agentx_terminal_reason": TERMINAL_REASON_BLOCKING_LIMIT,
132
+ },
133
+ )
134
+ if name:
135
+ blocked_ai.name = name
136
+ handled = handle_model_output(
137
+ blocked_ai,
138
+ initial_response_format,
139
+ structured_output_tools,
140
+ )
141
+ return _ModelResponse(
142
+ result=handled["messages"],
143
+ structured_response=handled.get("structured_response"),
144
+ )
145
+
146
+
147
+ class _ModelNodeBuilder:
148
+ """同步/异步 model 节点构建器。"""
149
+
150
+ def __init__(
151
+ self,
152
+ *,
153
+ model: Any,
154
+ default_tools: List[Any],
155
+ system_message: Any,
156
+ initial_response_format: ResponseFormat[Any] | None,
157
+ max_steps: int | None,
158
+ max_turns: int | None,
159
+ max_withhold_retries: int,
160
+ name: str | None,
161
+ structured_output_tools: Dict[str, Any],
162
+ tool_strategy_for_setup: Any,
163
+ retrier: ModelCallRetrier | None,
164
+ tool_node: Any,
165
+ dynamic_tool_error_template: str,
166
+ fallback_model: Any | None,
167
+ llm_callback_handler: Any | None,
168
+ compaction_settings: CompactionSettings | None,
169
+ max_output_tokens_cap: int | None,
170
+ ) -> None:
171
+ self._model = model
172
+ self._default_tools = default_tools
173
+ self._system_message = system_message
174
+ self._initial_response_format = initial_response_format
175
+ self._max_steps = max_steps
176
+ self._max_turns = max_turns
177
+ self._max_withhold_retries = max_withhold_retries
178
+ self._name = name
179
+ self._structured_output_tools = structured_output_tools
180
+ self._tool_strategy_for_setup = tool_strategy_for_setup
181
+ self._retrier = retrier
182
+ self._tool_node = tool_node
183
+ self._dynamic_tool_error_template = dynamic_tool_error_template
184
+ self._fallback_model = fallback_model
185
+ self._llm_callback_handler = llm_callback_handler
186
+ self._dynamic_sections: list[Any] = []
187
+ self._base_prompt: str | None = None
188
+ self._override_prompt: str | None = None
189
+ self._append_prompt: str | None = None
190
+ self._has_volatile_sections: bool = False
191
+ self._compaction_settings = compaction_settings
192
+ self._max_output_tokens_cap = max_output_tokens_cap
193
+
194
+ def build_sync(
195
+ self,
196
+ ) -> Callable[[AgentState[Any], Runtime[ContextT]], List[Command[Any]]]:
197
+ def model_node(
198
+ state: AgentState[Any],
199
+ runtime: Runtime[ContextT],
200
+ config: RunnableConfig | None = None,
201
+ ) -> List[Command[Any]]:
202
+ request = self._build_request(state, runtime)
203
+ model_response, retry_event_commands = self._run_sync_with_retries(
204
+ request,
205
+ state,
206
+ config=config,
207
+ )
208
+ return self._build_commands_from_response(
209
+ state=state,
210
+ model_response=model_response,
211
+ retry_event_commands=retry_event_commands,
212
+ mode="sync",
213
+ )
214
+
215
+ return model_node
216
+
217
+ def build_async(
218
+ self,
219
+ ) -> Callable[[AgentState[Any], Runtime[ContextT]], Any]:
220
+ async def amodel_node(
221
+ state: AgentState[Any],
222
+ runtime: Runtime[ContextT],
223
+ config: RunnableConfig | None = None,
224
+ ) -> List[Command[Any]]:
225
+ request = self._build_request(state, runtime)
226
+ model_response, retry_event_commands = await self._run_async_with_retries(
227
+ request,
228
+ state,
229
+ config=config,
230
+ )
231
+ return self._build_commands_from_response(
232
+ state=state,
233
+ model_response=model_response,
234
+ retry_event_commands=retry_event_commands,
235
+ mode="async",
236
+ )
237
+
238
+ return amodel_node
239
+
240
+ def _build_request(self, state: AgentState[Any], runtime: Runtime[ContextT]) -> Any:
241
+ from langchain.agents.middleware.types import ModelRequest as _ModelRequest
242
+
243
+ model_settings: Dict[str, Any] = {}
244
+ if state.get("max_output_tokens_override") is not None:
245
+ requested_override = int(state["max_output_tokens_override"])
246
+ cap = self._max_output_tokens_cap
247
+ if cap is not None:
248
+ requested_override = min(requested_override, int(cap))
249
+ model_settings["max_tokens"] = requested_override
250
+
251
+ system_message = self._system_message
252
+ if self._has_volatile_sections:
253
+ from ..prompt.builder import build_effective_system_prompt
254
+ from langchain_core.messages import SystemMessage as _SystemMessage
255
+ section_cache: dict[str, Any] = state.get("section_cache") or {}
256
+ effective = build_effective_system_prompt(
257
+ system_prompt=self._base_prompt,
258
+ override_system_prompt=self._override_prompt,
259
+ append_system_prompt=self._append_prompt,
260
+ dynamic_sections=self._dynamic_sections,
261
+ section_cache=section_cache,
262
+ )
263
+ system_message = _SystemMessage(content=effective) if effective is not None else None
264
+
265
+ return _ModelRequest(
266
+ model=self._model,
267
+ tools=self._default_tools,
268
+ system_message=system_message,
269
+ response_format=self._initial_response_format,
270
+ messages=state["messages"],
271
+ tool_choice=None,
272
+ state=state,
273
+ runtime=runtime,
274
+ model_settings=model_settings,
275
+ )
276
+
277
+ def _invoke_bound_sync(self, req: Any, *, config: RunnableConfig | None = None) -> Any:
278
+ from langchain.agents.middleware.types import ModelResponse as _ModelResponse
279
+
280
+ model_, effective_response_format = get_bound_model(
281
+ request=req,
282
+ structured_output_tools=self._structured_output_tools,
283
+ initial_response_format=self._initial_response_format,
284
+ tool_strategy_for_setup=self._tool_strategy_for_setup,
285
+ tool_node=self._tool_node,
286
+ wrap_tool_call_wrapper=None,
287
+ awrap_tool_call_wrapper=None,
288
+ dynamic_tool_error_template=self._dynamic_tool_error_template,
289
+ )
290
+ model_ = self._bind_llm_callback_handler(model_, config)
291
+ messages = _messages_for_model_invoke(req)
292
+ output = model_.invoke(messages, config=config)
293
+ if self._name:
294
+ output.name = self._name
295
+ handled_output = handle_model_output(
296
+ output=output,
297
+ effective_response_format=effective_response_format,
298
+ structured_output_tools=self._structured_output_tools,
299
+ )
300
+ messages_list = handled_output["messages"]
301
+ _inject_model_into_agent_tool_calls(messages_list, req.model)
302
+ return _ModelResponse(
303
+ result=messages_list,
304
+ structured_response=handled_output.get("structured_response"),
305
+ )
306
+
307
+ async def _invoke_bound_async(
308
+ self,
309
+ req: Any,
310
+ *,
311
+ config: RunnableConfig | None = None,
312
+ ) -> Any:
313
+ from langchain.agents.middleware.types import ModelResponse as _ModelResponse
314
+
315
+ model_, effective_response_format = get_bound_model(
316
+ request=req,
317
+ structured_output_tools=self._structured_output_tools,
318
+ initial_response_format=self._initial_response_format,
319
+ tool_strategy_for_setup=self._tool_strategy_for_setup,
320
+ tool_node=self._tool_node,
321
+ wrap_tool_call_wrapper=None,
322
+ awrap_tool_call_wrapper=None,
323
+ dynamic_tool_error_template=self._dynamic_tool_error_template,
324
+ )
325
+ model_ = self._bind_llm_callback_handler(model_, config)
326
+ messages = _messages_for_model_invoke(req)
327
+ output = await model_.ainvoke(messages, config=config)
328
+ if self._name:
329
+ output.name = self._name
330
+ handled_output = handle_model_output(
331
+ output=output,
332
+ effective_response_format=effective_response_format,
333
+ structured_output_tools=self._structured_output_tools,
334
+ )
335
+ messages_list = handled_output["messages"]
336
+ _inject_model_into_agent_tool_calls(messages_list, req.model)
337
+ return _ModelResponse(
338
+ result=messages_list,
339
+ structured_response=handled_output.get("structured_response"),
340
+ )
341
+
342
+ def _bind_llm_callback_handler(self, model: Any, config: RunnableConfig | None) -> Any:
343
+ if self._llm_callback_handler is None:
344
+ return model
345
+ callbacks: list[Any] = []
346
+ if isinstance(config, dict):
347
+ configured_callbacks = config.get("callbacks")
348
+ if isinstance(configured_callbacks, list):
349
+ callbacks.extend(configured_callbacks)
350
+ elif configured_callbacks is not None:
351
+ manager_handlers = getattr(configured_callbacks, "handlers", None)
352
+ if isinstance(manager_handlers, list):
353
+ callbacks.extend(manager_handlers)
354
+ if self._llm_callback_handler not in callbacks:
355
+ callbacks.append(self._llm_callback_handler)
356
+ return model.with_config({"callbacks": callbacks})
357
+
358
+ def _run_sync_with_retries(
359
+ self,
360
+ request: Any,
361
+ state: AgentState[Any],
362
+ *,
363
+ config: RunnableConfig | None = None,
364
+ ) -> tuple[Any, list[Command[Any]]]:
365
+ synth = _model_response_if_context_token_blocked(
366
+ state_messages=state["messages"],
367
+ name=self._name,
368
+ initial_response_format=self._initial_response_format,
369
+ structured_output_tools=self._structured_output_tools,
370
+ compaction_settings=self._compaction_settings,
371
+ )
372
+ if synth is not None:
373
+ return synth, []
374
+ retry_event_commands: list[Command[Any]] = []
375
+ try:
376
+ if self._retrier is None:
377
+ return self._invoke_bound_sync(request, config=config), retry_event_commands
378
+ retry_ctx = RetryContext(
379
+ scope="background" if state.get("retry_scope") == "background" else "foreground"
380
+ )
381
+ retry_result = self._retrier.invoke(
382
+ lambda: self._invoke_bound_sync(request, config=config),
383
+ context=retry_ctx,
384
+ )
385
+ retry_event_commands = build_retry_event_commands(retry_result.events)
386
+ return retry_result.value, retry_event_commands
387
+ except Exception as primary_exc:
388
+ if self._fallback_model is None:
389
+ return _synthetic_api_error_model_response(primary_exc, self._name), retry_event_commands
390
+ logger.warning(
391
+ "Primary model invoke failed; retrying once with fallback_model",
392
+ exc_info=True,
393
+ )
394
+ req_fb = copy.copy(request)
395
+ req_fb.model = self._fallback_model
396
+ try:
397
+ if self._retrier is None:
398
+ return self._invoke_bound_sync(req_fb, config=config), retry_event_commands
399
+ fb_result = self._retrier.invoke(
400
+ lambda: self._invoke_bound_sync(req_fb, config=config),
401
+ context=retry_ctx,
402
+ )
403
+ retry_event_commands.extend(build_retry_event_commands(fb_result.events))
404
+ return fb_result.value, retry_event_commands
405
+ except Exception as fb_exc:
406
+ return _synthetic_api_error_model_response(fb_exc, self._name), retry_event_commands
407
+
408
+ async def _run_async_with_retries(
409
+ self,
410
+ request: Any,
411
+ state: AgentState[Any],
412
+ *,
413
+ config: RunnableConfig | None = None,
414
+ ) -> tuple[Any, list[Command[Any]]]:
415
+ synth = _model_response_if_context_token_blocked(
416
+ state_messages=state["messages"],
417
+ name=self._name,
418
+ initial_response_format=self._initial_response_format,
419
+ structured_output_tools=self._structured_output_tools,
420
+ compaction_settings=self._compaction_settings,
421
+ )
422
+ if synth is not None:
423
+ return synth, []
424
+ retry_event_commands: list[Command[Any]] = []
425
+ try:
426
+ if self._retrier is None:
427
+ return await self._invoke_bound_async(request, config=config), retry_event_commands
428
+ retry_ctx = RetryContext(
429
+ scope="background" if state.get("retry_scope") == "background" else "foreground"
430
+ )
431
+ retry_result = await self._retrier.ainvoke(
432
+ lambda: self._invoke_bound_async(request, config=config),
433
+ context=retry_ctx,
434
+ )
435
+ retry_event_commands = build_retry_event_commands(retry_result.events)
436
+ return retry_result.value, retry_event_commands
437
+ except Exception as primary_exc:
438
+ if self._fallback_model is None:
439
+ return _synthetic_api_error_model_response(primary_exc, self._name), retry_event_commands
440
+ logger.warning(
441
+ "Primary model ainvoke failed; retrying once with fallback_model",
442
+ exc_info=True,
443
+ )
444
+ req_fb = copy.copy(request)
445
+ req_fb.model = self._fallback_model
446
+ try:
447
+ if self._retrier is None:
448
+ return await self._invoke_bound_async(req_fb, config=config), retry_event_commands
449
+ fb_result = await self._retrier.ainvoke(
450
+ lambda: self._invoke_bound_async(req_fb, config=config),
451
+ context=retry_ctx,
452
+ )
453
+ retry_event_commands.extend(build_retry_event_commands(fb_result.events))
454
+ return fb_result.value, retry_event_commands
455
+ except Exception as fb_exc:
456
+ return _synthetic_api_error_model_response(fb_exc, self._name), retry_event_commands
457
+
458
+ def _build_commands_from_response(
459
+ self,
460
+ *,
461
+ state: AgentState[Any],
462
+ model_response: Any,
463
+ retry_event_commands: list[Command[Any]],
464
+ mode: str,
465
+ ) -> List[Command[Any]]:
466
+ last_ai_message = self._find_last_ai_message(model_response.result)
467
+ should_end, terminal_reason = determine_should_end(
468
+ state, last_ai_message, self._max_steps, self._max_turns
469
+ )
470
+ finish_reason = extract_finish_reason(last_ai_message)
471
+ self._log_stop_if_needed(
472
+ should_end=should_end,
473
+ terminal_reason=terminal_reason,
474
+ mode=mode,
475
+ step=state.get("step", 0) + 1,
476
+ finish_reason=finish_reason,
477
+ )
478
+ finish_reasons = self._append_finish_reason(state, finish_reason)
479
+ recovery_update = self._resolve_withhold_recovery_update(
480
+ state=state,
481
+ should_end=should_end,
482
+ terminal_reason=terminal_reason,
483
+ finish_reason=finish_reason,
484
+ )
485
+ state_update: Dict[str, Any] = {
486
+ "step": state.get("step", 0) + 1,
487
+ "should_end": recovery_update["should_end"],
488
+ "finish_reason": finish_reason,
489
+ "terminal_reason": recovery_update["terminal_reason"],
490
+ "withheld_error": recovery_update["withheld_error"],
491
+ "withhold_retry_count": recovery_update["withhold_retry_count"],
492
+ "max_output_tokens_override": recovery_update["max_output_tokens_override"],
493
+ "max_output_tokens_recovery_count": recovery_update["max_output_tokens_recovery_count"],
494
+ "transition": recovery_update["transition"],
495
+ "finish_reasons": finish_reasons,
496
+ }
497
+ base_state: Dict[str, Any] = {"messages": model_response.result}
498
+ if model_response.structured_response is not None:
499
+ base_state["structured_response"] = model_response.structured_response
500
+ state_update["structured_response"] = model_response.structured_response
501
+ return [*retry_event_commands, Command(update=base_state), Command(update=state_update)]
502
+
503
+ def _find_last_ai_message(self, messages: list[Any]) -> AIMessage | None:
504
+ for msg in reversed(messages):
505
+ if isinstance(msg, AIMessage):
506
+ return msg
507
+ return None
508
+
509
+ def _log_stop_if_needed(
510
+ self,
511
+ *,
512
+ should_end: bool,
513
+ terminal_reason: str | None,
514
+ mode: str,
515
+ step: int,
516
+ finish_reason: str | None,
517
+ ) -> None:
518
+ if not should_end:
519
+ return
520
+ logger.info(
521
+ "LangChain-AgentX agent stopping (%s) | terminal_reason=%s, step=%s, finish_reason=%s",
522
+ mode,
523
+ terminal_reason,
524
+ step,
525
+ finish_reason,
526
+ )
527
+
528
+ def _append_finish_reason(
529
+ self,
530
+ state: AgentState[Any],
531
+ finish_reason: str | None,
532
+ ) -> list[str]:
533
+ finish_reasons = list(state.get("finish_reasons", []))
534
+ if finish_reason:
535
+ finish_reasons.append(finish_reason)
536
+ return finish_reasons
537
+
538
+ def _resolve_withhold_recovery_update(
539
+ self,
540
+ *,
541
+ state: AgentState[Any],
542
+ should_end: bool,
543
+ terminal_reason: str | None,
544
+ finish_reason: str | None,
545
+ ) -> Dict[str, Any]:
546
+ withheld_error = state.get("withheld_error")
547
+ withhold_retry_count = int(state.get("withhold_retry_count", 0) or 0)
548
+ max_output_tokens_override = state.get("max_output_tokens_override")
549
+ max_output_tokens_recovery_count = int(state.get("max_output_tokens_recovery_count", 0) or 0)
550
+ can_default_withhold_retry = withhold_retry_count < self._max_withhold_retries
551
+ can_max_tokens_recovery_retry = (
552
+ terminal_reason == TERMINAL_REASON_MAX_TOKENS
553
+ and max_output_tokens_override is not None
554
+ and max_output_tokens_recovery_count < MAX_OUTPUT_TOKENS_RECOVERY_LIMIT
555
+ )
556
+ if should_end and terminal_reason in RECOVERABLE_TERMINAL_REASONS and (
557
+ can_default_withhold_retry or can_max_tokens_recovery_retry
558
+ ):
559
+ withheld_error = {"reason": terminal_reason, "finish_reason": finish_reason}
560
+ if can_default_withhold_retry:
561
+ withhold_retry_count += 1
562
+ return {
563
+ "should_end": False,
564
+ "terminal_reason": None,
565
+ "withheld_error": withheld_error,
566
+ "withhold_retry_count": withhold_retry_count,
567
+ "max_output_tokens_override": max_output_tokens_override,
568
+ "max_output_tokens_recovery_count": max_output_tokens_recovery_count,
569
+ "transition": {
570
+ "reason": TRANSITION_WITHHOLD_RECOVERY_RETRY,
571
+ "error_reason": withheld_error["reason"],
572
+ "attempt": withhold_retry_count,
573
+ },
574
+ }
575
+ if should_end and terminal_reason in RECOVERABLE_TERMINAL_REASONS:
576
+ return {
577
+ "should_end": should_end,
578
+ "terminal_reason": terminal_reason,
579
+ "withheld_error": None,
580
+ "withhold_retry_count": withhold_retry_count,
581
+ "max_output_tokens_override": max_output_tokens_override,
582
+ "max_output_tokens_recovery_count": max_output_tokens_recovery_count,
583
+ "transition": {
584
+ "reason": TRANSITION_WITHHOLD_RECOVERY_EXHAUSTED,
585
+ "error_reason": terminal_reason,
586
+ "attempt": withhold_retry_count,
587
+ },
588
+ }
589
+ if terminal_reason != TERMINAL_REASON_MAX_TOKENS:
590
+ max_output_tokens_override = None
591
+ max_output_tokens_recovery_count = 0
592
+ if not should_end:
593
+ withhold_retry_count = 0
594
+ return {
595
+ "should_end": should_end,
596
+ "terminal_reason": terminal_reason,
597
+ "withheld_error": None,
598
+ "withhold_retry_count": withhold_retry_count,
599
+ "max_output_tokens_override": max_output_tokens_override,
600
+ "max_output_tokens_recovery_count": max_output_tokens_recovery_count,
601
+ "transition": None,
602
+ }
603
+
604
+
605
+ def make_model_nodes(
606
+ *,
607
+ model: Any,
608
+ default_tools: List[Any],
609
+ system_message: Any,
610
+ initial_response_format: ResponseFormat[Any] | None,
611
+ max_steps: int | None,
612
+ max_turns: int | None,
613
+ max_withhold_retries: int = 1,
614
+ name: str | None,
615
+ structured_output_tools: Dict[str, Any],
616
+ tool_strategy_for_setup: Any,
617
+ retrier: ModelCallRetrier | None,
618
+ tool_node: Any,
619
+ dynamic_tool_error_template: str,
620
+ fallback_model: Any | None = None,
621
+ llm_callback_handler: Any | None = None,
622
+ dynamic_sections: List[Any] | None = None,
623
+ base_prompt: str | None = None,
624
+ override_prompt: str | None = None,
625
+ append_prompt: str | None = None,
626
+ compaction_settings: CompactionSettings | None = None,
627
+ max_output_tokens_cap: int | None = None,
628
+ ) -> Tuple[Callable[..., Any], Callable[..., Any]]:
629
+ """根据传入配置构造 sync/async model 节点函数。
630
+
631
+ 当前使用 ModelNode(Runnable) 内部实现,保证 on_chat_model_stream 事件可透传。
632
+ _ModelNodeBuilder 保留做兼容,新增功能请直接在 ModelNode 上扩展。
633
+ """
634
+ node = ModelNode(
635
+ model=model,
636
+ default_tools=default_tools,
637
+ system_message=system_message,
638
+ initial_response_format=initial_response_format,
639
+ max_steps=max_steps,
640
+ max_turns=max_turns,
641
+ max_withhold_retries=max_withhold_retries,
642
+ name=name,
643
+ structured_output_tools=structured_output_tools,
644
+ tool_strategy_for_setup=tool_strategy_for_setup,
645
+ retrier=retrier,
646
+ tool_node=tool_node,
647
+ dynamic_tool_error_template=dynamic_tool_error_template,
648
+ fallback_model=fallback_model,
649
+ llm_callback_handler=llm_callback_handler,
650
+ compaction_settings=compaction_settings,
651
+ max_output_tokens_cap=max_output_tokens_cap,
652
+ dynamic_sections=dynamic_sections,
653
+ base_prompt=base_prompt,
654
+ override_prompt=override_prompt,
655
+ append_prompt=append_prompt,
656
+ )
657
+ return node.invoke, node.ainvoke
658
+
659
+
660
+ __all__ = ["make_model_nodes"]
661
+
@@ -0,0 +1,38 @@
1
+ """
2
+ 流式中断 / 工具中断时 orphan tool_use 的 synthetic tool_result 占位(CC §5.3 / §5.6)。
3
+
4
+ 与 `ToolNode` 执行路径解耦:完整注入应在 **tools 节点后**或 **model 节点**检测 abort 后调用。
5
+ 当前仅提供契约与扩展点,避免与现有 LangGraph `Send` 并行语义冲突。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from langchain_core.messages import ToolMessage
13
+
14
+
15
+ def synthetic_tool_messages_for_orphan_tool_calls(
16
+ *,
17
+ pending_tool_calls: list[dict[str, Any]],
18
+ content_template: str = "[Tool call aborted or stream interrupted; synthetic result.]",
19
+ ) -> list[ToolMessage]:
20
+ """为仍挂在最后一条 AIMessage 上、但未收到 ToolMessage 的 tool_call 生成占位结果。
21
+
22
+ 供流式中断、abort、synthetic 兜底路径组装 `messages` 时使用;**不**代替 ToolNode 的真实执行。
23
+ """
24
+ out: list[ToolMessage] = []
25
+ for tc in pending_tool_calls:
26
+ tid = tc.get("id") or ""
27
+ name = str(tc.get("name") or "unknown_tool")
28
+ out.append(
29
+ ToolMessage(
30
+ content=content_template,
31
+ tool_call_id=tid,
32
+ name=name,
33
+ )
34
+ )
35
+ return out
36
+
37
+
38
+ __all__ = ["synthetic_tool_messages_for_orphan_tool_calls"]