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,648 @@
1
+ """
2
+ model_node.py — ModelNode:模型调用图节点
3
+
4
+ 职责:
5
+ 作为 LangGraph 的模型调用图节点,封装模型调用、重试与降级、
6
+ ToolCall 退化纠偏、退出逻辑判断、事件透传。
7
+
8
+ 链路位置:
9
+ factory._create_graph_skeleton() → graph.add_node("model", ModelNode(...))
10
+ → 替代原有 RunnableCallable(model_node, amodel_node, trace=False)
11
+
12
+ 当前裁剪范围:
13
+ - 不实现 stream/astream/transform(模型节点不走流式输出)
14
+ - 不改变 graph 拓扑结构(node name、edge 连接方式不变)
15
+ - 不处理多模型节点标记(fallback 调用目前无额外标记)
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ from typing import Any, Dict, List
22
+
23
+ from langchain.agents.structured_output import ResponseFormat
24
+ from langchain_core.messages import AIMessage, SystemMessage
25
+ from langchain_core.runnables import Runnable
26
+ from langchain_core.runnables.config import RunnableConfig
27
+ from langgraph.types import Command
28
+
29
+ from ..config.runtime_settings import materialize_max_steps
30
+ from ..context.settings import CompactionSettings, get_active_compaction_settings
31
+ from ..exit.exit_logic import (
32
+ MAX_OUTPUT_TOKENS_RECOVERY_LIMIT,
33
+ RECOVERABLE_TERMINAL_REASONS,
34
+ determine_should_end,
35
+ extract_finish_reason,
36
+ )
37
+ from ..exit.reason_codes import (
38
+ TERMINAL_REASON_BLOCKING_LIMIT,
39
+ TERMINAL_REASON_MAX_TOKENS,
40
+ TRANSITION_WITHHOLD_RECOVERY_EXHAUSTED,
41
+ TRANSITION_WITHHOLD_RECOVERY_RETRY,
42
+ )
43
+ from .retrier import ModelCallRetrier, RetryContext
44
+ from .retry_bridge import build_retry_event_commands
45
+ from .tool_and_model_binding import get_bound_model, handle_model_output
46
+ from .tool_transcript_guard import repair_messages_for_llm_invoke
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+ # =========================================================================
51
+ # 辅助函数(从 model_nodes.py 迁移,保持语义一致)
52
+ # =========================================================================
53
+
54
+
55
+ def _synthetic_api_error_model_response(exc: Exception, name: str | None) -> Any:
56
+ """invoke/ainvoke 最终失败时合成 AIMessage,供 loop_controller CC §5.7 优先级 4 识别。"""
57
+ from langchain.agents.middleware.types import ModelResponse as _ModelResponse
58
+
59
+ meta = {
60
+ "finish_reason": "error",
61
+ "langchain_agentx_api_error": True,
62
+ "error": {
63
+ "type": type(exc).__name__,
64
+ "message": str(exc),
65
+ },
66
+ }
67
+ ai = AIMessage(content=f"[LLM request failed] {exc}", response_metadata=meta)
68
+ if name:
69
+ ai.name = name
70
+ return _ModelResponse(result=[ai], structured_response=None)
71
+
72
+
73
+ def _inject_model_into_agent_tool_calls(messages: list[Any], model: Any) -> None:
74
+ """在模型输出阶段为 Agent 工具调用补齐 model/tool_call_id 入参。"""
75
+ if not isinstance(model, str):
76
+ return
77
+ for msg in messages:
78
+ tool_calls = getattr(msg, "tool_calls", None)
79
+ if not isinstance(tool_calls, list):
80
+ continue
81
+ for tc in tool_calls:
82
+ if not isinstance(tc, dict) or tc.get("name") != "Agent":
83
+ continue
84
+ args = tc.get("args")
85
+ if not isinstance(args, dict):
86
+ continue
87
+ if not args.get("model"):
88
+ args["model"] = model
89
+ call_id = tc.get("id")
90
+ if isinstance(call_id, str) and call_id and not args.get("tool_call_id"):
91
+ args["tool_call_id"] = call_id
92
+
93
+
94
+ def _messages_for_model_invoke(req: Any) -> list[Any]:
95
+ """组装本次 LLM 调用使用的消息列表;可选在 invoke 前做 tool 管线修复。"""
96
+ messages = list(req.messages or [])
97
+ s = get_active_compaction_settings()
98
+ if getattr(s, "enable_tool_transcript_repair_before_llm", True):
99
+ messages, stats = repair_messages_for_llm_invoke(messages)
100
+ if stats.get("repaired"):
101
+ logger.debug("tool_transcript_guard applied | %s", stats)
102
+ if req.system_message:
103
+ return [req.system_message, *messages]
104
+ return messages
105
+
106
+
107
+ def _model_response_if_context_token_blocked(
108
+ *,
109
+ state_messages: List[Any],
110
+ name: str | None,
111
+ initial_response_format: ResponseFormat[Any] | None,
112
+ structured_output_tools: Dict[str, Any],
113
+ compaction_settings: CompactionSettings | None,
114
+ ) -> Any | None:
115
+ """若主上下文启发式已超过 CompactionSettings 顶线,返回合成 ModelResponse,否则 None。"""
116
+ from langchain_agentx.loop.context.blocking_guard import CompactionBlockingGuard
117
+ from langchain_agentx.loop.context.types import ContextCompactionContext
118
+ from langchain.agents.middleware.types import ModelResponse as _ModelResponse
119
+
120
+ s = compaction_settings or get_active_compaction_settings()
121
+ if not s.enable_main_model_context_token_block:
122
+ return None
123
+ guard = CompactionBlockingGuard(settings=s)
124
+ est = guard.estimate_messages_tokens(state_messages)
125
+ if not guard.should_block(ContextCompactionContext(), estimated_tokens=est):
126
+ return None
127
+ tw = guard.calculate_token_warning_state(state_messages)
128
+ blocked_ai = AIMessage(
129
+ content=(
130
+ "[Stopped before LLM call: estimated conversation tokens "
131
+ f"({tw.estimated_tokens}) reached or exceeded the configured "
132
+ f"context ceiling ({tw.block_main_model_threshold_tokens}).]"
133
+ ),
134
+ response_metadata={
135
+ "finish_reason": "stop",
136
+ "langchain_agentx_terminal_reason": TERMINAL_REASON_BLOCKING_LIMIT,
137
+ },
138
+ )
139
+ if name:
140
+ blocked_ai.name = name
141
+ handled = handle_model_output(
142
+ blocked_ai,
143
+ initial_response_format,
144
+ structured_output_tools,
145
+ )
146
+ return _ModelResponse(
147
+ result=handled["messages"],
148
+ structured_response=handled.get("structured_response"),
149
+ )
150
+
151
+
152
+ # =========================================================================
153
+ # ModelNode — 模型调用图节点
154
+ # =========================================================================
155
+
156
+
157
+ class ModelNode(Runnable):
158
+ """模型调用图节点。
159
+
160
+ 语义上标识自己是一个"模型节点",让 LangGraph 的事件框架
161
+ 能识别内部存在 chat_model run,从而正确发射 on_chat_model_stream。
162
+
163
+ 继承 Runnable 以避免 LangGraph 自动包装为 RunnableCallable,
164
+ 从而保持 on_chat_model_stream 事件传播。
165
+
166
+ 职责:
167
+ - 模型调用(invoke / ainvoke)
168
+ - 重试与降级
169
+ - ToolCall 退化纠偏
170
+ - 退出逻辑判断
171
+ - 事件透传(不创建额外的 run 上下文)
172
+ - 结构化输出转换
173
+ - withhold recovery 状态机
174
+ """
175
+
176
+ def __init__(
177
+ self,
178
+ *,
179
+ model: Any,
180
+ default_tools: List[Any],
181
+ system_message: Any,
182
+ initial_response_format: ResponseFormat[Any] | None,
183
+ max_steps: int | None,
184
+ max_turns: int | None,
185
+ max_withhold_retries: int,
186
+ name: str | None,
187
+ structured_output_tools: Dict[str, Any],
188
+ tool_strategy_for_setup: Any,
189
+ retrier: ModelCallRetrier | None,
190
+ tool_node: Any,
191
+ dynamic_tool_error_template: str,
192
+ fallback_model: Any | None = None,
193
+ llm_callback_handler: Any | None = None,
194
+ compaction_settings: CompactionSettings | None = None,
195
+ max_output_tokens_cap: int | None = None,
196
+ dynamic_sections: list[Any] | None = None,
197
+ base_prompt: str | None = None,
198
+ override_prompt: str | None = None,
199
+ append_prompt: str | None = None,
200
+ ) -> None:
201
+ self._model = model
202
+ self._default_tools = default_tools
203
+ self._system_message = system_message
204
+ self._initial_response_format = initial_response_format
205
+ self._max_steps = max_steps
206
+ self._effective_max_steps = materialize_max_steps(max_steps)
207
+ self._max_turns = max_turns
208
+ self._max_withhold_retries = max_withhold_retries
209
+ self._name = name
210
+ self._structured_output_tools = structured_output_tools
211
+ self._tool_strategy_for_setup = tool_strategy_for_setup
212
+ self._retrier = retrier
213
+ self._tool_node = tool_node
214
+ self._dynamic_tool_error_template = dynamic_tool_error_template
215
+ self._fallback_model = fallback_model
216
+ self._llm_callback_handler = llm_callback_handler
217
+ self._compaction_settings = compaction_settings
218
+ self._max_output_tokens_cap = max_output_tokens_cap
219
+ self._dynamic_sections = list(dynamic_sections) if dynamic_sections else []
220
+ self._base_prompt = base_prompt
221
+ self._override_prompt = override_prompt
222
+ self._append_prompt = append_prompt
223
+ self._has_volatile_sections = any(
224
+ getattr(s, "volatile", False) for s in (dynamic_sections or [])
225
+ )
226
+
227
+ # ===== Runnable 接口 =====
228
+
229
+ def invoke(
230
+ self, input: dict, config: RunnableConfig | None = None, **kwargs: Any
231
+ ) -> dict:
232
+ """Runnable 接口要求的 invoke 方法。
233
+
234
+ 注意:不创建额外 run 上下文,让 config 中的 callback 直接穿透到内部 model.invoke。
235
+ """
236
+ return self._run(input, config or {})
237
+
238
+ async def ainvoke(
239
+ self, input: dict, config: RunnableConfig | None = None, **kwargs: Any
240
+ ) -> dict:
241
+ """异步版本。"""
242
+ return await self._arun(input, config or {})
243
+
244
+ # ===== 核心执行路径 =====
245
+
246
+ def _run(self, state: dict, config: RunnableConfig) -> dict:
247
+ """核心同步执行逻辑。"""
248
+ runtime = config.get("configurable", {}).get("__pregel_runtime")
249
+ request = self._build_request(state, runtime)
250
+
251
+ synth = _model_response_if_context_token_blocked(
252
+ state_messages=state.get("messages", []),
253
+ name=self._name,
254
+ initial_response_format=self._initial_response_format,
255
+ structured_output_tools=self._structured_output_tools,
256
+ compaction_settings=self._compaction_settings,
257
+ )
258
+ if synth is not None:
259
+ model_response, retry_event_commands = synth, []
260
+ else:
261
+ model_response, retry_event_commands = self._run_sync(request, state, config=config)
262
+
263
+ return self._build_state_update(
264
+ state=state,
265
+ model_response=model_response,
266
+ retry_event_commands=retry_event_commands,
267
+ mode="sync",
268
+ )
269
+
270
+ async def _arun(self, state: dict, config: RunnableConfig) -> dict:
271
+ """核心异步执行逻辑。"""
272
+ runtime = config.get("configurable", {}).get("__pregel_runtime")
273
+ request = self._build_request(state, runtime)
274
+
275
+ synth = _model_response_if_context_token_blocked(
276
+ state_messages=state.get("messages", []),
277
+ name=self._name,
278
+ initial_response_format=self._initial_response_format,
279
+ structured_output_tools=self._structured_output_tools,
280
+ compaction_settings=self._compaction_settings,
281
+ )
282
+ if synth is not None:
283
+ model_response, retry_event_commands = synth, []
284
+ else:
285
+ model_response, retry_event_commands = await self._arun_async(request, state, config=config)
286
+
287
+ return self._build_state_update(
288
+ state=state,
289
+ model_response=model_response,
290
+ retry_event_commands=retry_event_commands,
291
+ mode="async",
292
+ )
293
+
294
+ # ===== 请求构建 =====
295
+
296
+ def _build_request(self, state: dict, runtime: Any) -> Any:
297
+ """从 state 构建 _ModelRequest。"""
298
+ from langchain.agents.middleware.types import ModelRequest as _ModelRequest
299
+
300
+ model_settings: Dict[str, Any] = {}
301
+ if state.get("max_output_tokens_override") is not None:
302
+ requested_override = int(state["max_output_tokens_override"])
303
+ cap = self._max_output_tokens_cap
304
+ if cap is not None:
305
+ requested_override = min(requested_override, int(cap))
306
+ model_settings["max_tokens"] = requested_override
307
+
308
+ system_message = self._system_message
309
+ if self._has_volatile_sections:
310
+ from ..prompt.builder import build_effective_system_prompt
311
+
312
+ section_cache = state.get("section_cache") or {}
313
+ effective = build_effective_system_prompt(
314
+ system_prompt=self._base_prompt,
315
+ override_system_prompt=self._override_prompt,
316
+ append_system_prompt=self._append_prompt,
317
+ dynamic_sections=self._dynamic_sections,
318
+ section_cache=section_cache,
319
+ )
320
+ system_message = (
321
+ SystemMessage(content=effective) if effective is not None else None
322
+ )
323
+
324
+ return _ModelRequest(
325
+ model=self._model,
326
+ tools=self._default_tools,
327
+ system_message=system_message,
328
+ response_format=self._initial_response_format,
329
+ messages=state.get("messages", []),
330
+ tool_choice=None,
331
+ state=state,
332
+ runtime=runtime,
333
+ model_settings=model_settings,
334
+ )
335
+
336
+ # ===== 模型调用 =====
337
+
338
+ def _invoke_bound(self, req: Any, *, config: RunnableConfig | None = None) -> Any:
339
+ """单次模型同步调用(含 binding、callback handler、输出后处理)。"""
340
+ from langchain.agents.middleware.types import ModelResponse as _ModelResponse
341
+
342
+ model_, effective_response_format = get_bound_model(
343
+ request=req,
344
+ structured_output_tools=self._structured_output_tools,
345
+ initial_response_format=self._initial_response_format,
346
+ tool_strategy_for_setup=self._tool_strategy_for_setup,
347
+ tool_node=self._tool_node,
348
+ wrap_tool_call_wrapper=None,
349
+ awrap_tool_call_wrapper=None,
350
+ dynamic_tool_error_template=self._dynamic_tool_error_template,
351
+ )
352
+ model_ = self._bind_llm_callback_handler(model_, config)
353
+ messages = _messages_for_model_invoke(req)
354
+ output = model_.invoke(messages, config=config)
355
+ if self._name:
356
+ output.name = self._name
357
+ handled_output = handle_model_output(
358
+ output=output,
359
+ effective_response_format=effective_response_format,
360
+ structured_output_tools=self._structured_output_tools,
361
+ )
362
+ messages_list = handled_output["messages"]
363
+ _inject_model_into_agent_tool_calls(messages_list, req.model)
364
+ return _ModelResponse(
365
+ result=messages_list,
366
+ structured_response=handled_output.get("structured_response"),
367
+ )
368
+
369
+ async def _ainvoke_bound(self, req: Any, *, config: RunnableConfig | None = None) -> Any:
370
+ """单次模型异步调用。"""
371
+ from langchain.agents.middleware.types import ModelResponse as _ModelResponse
372
+
373
+ model_, effective_response_format = get_bound_model(
374
+ request=req,
375
+ structured_output_tools=self._structured_output_tools,
376
+ initial_response_format=self._initial_response_format,
377
+ tool_strategy_for_setup=self._tool_strategy_for_setup,
378
+ tool_node=self._tool_node,
379
+ wrap_tool_call_wrapper=None,
380
+ awrap_tool_call_wrapper=None,
381
+ dynamic_tool_error_template=self._dynamic_tool_error_template,
382
+ )
383
+ model_ = self._bind_llm_callback_handler(model_, config)
384
+ messages = _messages_for_model_invoke(req)
385
+ output = await model_.ainvoke(messages, config=config)
386
+ if self._name:
387
+ output.name = self._name
388
+ handled_output = handle_model_output(
389
+ output=output,
390
+ effective_response_format=effective_response_format,
391
+ structured_output_tools=self._structured_output_tools,
392
+ )
393
+ messages_list = handled_output["messages"]
394
+ _inject_model_into_agent_tool_calls(messages_list, req.model)
395
+ return _ModelResponse(
396
+ result=messages_list,
397
+ structured_response=handled_output.get("structured_response"),
398
+ )
399
+
400
+ def _bind_llm_callback_handler(self, model: Any, config: RunnableConfig | None) -> Any:
401
+ """将 llm_callback_handler 注入模型调用的 callbacks。"""
402
+ if self._llm_callback_handler is None:
403
+ return model
404
+ callbacks: list[Any] = []
405
+ if isinstance(config, dict):
406
+ configured_callbacks = config.get("callbacks")
407
+ if isinstance(configured_callbacks, list):
408
+ callbacks.extend(configured_callbacks)
409
+ elif configured_callbacks is not None:
410
+ manager_handlers = getattr(configured_callbacks, "handlers", None)
411
+ if isinstance(manager_handlers, list):
412
+ callbacks.extend(manager_handlers)
413
+ if self._llm_callback_handler not in callbacks:
414
+ callbacks.append(self._llm_callback_handler)
415
+ return model.with_config({"callbacks": callbacks})
416
+
417
+ # ===== 重试与降级 =====
418
+
419
+ def _run_sync(
420
+ self,
421
+ request: Any,
422
+ state: dict,
423
+ *,
424
+ config: RunnableConfig | None = None,
425
+ ) -> tuple[Any, list[Command]]:
426
+ """同步重试与降级执行。"""
427
+ retry_event_commands: list[Command] = []
428
+ try:
429
+ if self._retrier is None:
430
+ return self._invoke_bound(request, config=config), retry_event_commands
431
+ retry_ctx = RetryContext(
432
+ scope="background" if state.get("retry_scope") == "background" else "foreground"
433
+ )
434
+ retry_result = self._retrier.invoke(
435
+ lambda: self._invoke_bound(request, config=config),
436
+ context=retry_ctx,
437
+ )
438
+ retry_event_commands = build_retry_event_commands(retry_result.events)
439
+ return retry_result.value, retry_event_commands
440
+ except Exception as primary_exc:
441
+ if self._fallback_model is None:
442
+ return _synthetic_api_error_model_response(primary_exc, self._name), retry_event_commands
443
+ logger.warning(
444
+ "Primary model invoke failed; retrying once with fallback_model",
445
+ exc_info=True,
446
+ )
447
+ req_fb = request.override(model=self._fallback_model)
448
+ try:
449
+ if self._retrier is None:
450
+ return self._invoke_bound(req_fb, config=config), retry_event_commands
451
+ fb_result = self._retrier.invoke(
452
+ lambda: self._invoke_bound(req_fb, config=config),
453
+ context=retry_ctx,
454
+ )
455
+ retry_event_commands.extend(build_retry_event_commands(fb_result.events))
456
+ return fb_result.value, retry_event_commands
457
+ except Exception as fb_exc:
458
+ return _synthetic_api_error_model_response(fb_exc, self._name), retry_event_commands
459
+
460
+ async def _arun_async(
461
+ self,
462
+ request: Any,
463
+ state: dict,
464
+ *,
465
+ config: RunnableConfig | None = None,
466
+ ) -> tuple[Any, list[Command]]:
467
+ """异步重试与降级执行。"""
468
+ retry_event_commands: list[Command] = []
469
+ try:
470
+ if self._retrier is None:
471
+ return await self._ainvoke_bound(request, config=config), retry_event_commands
472
+ retry_ctx = RetryContext(
473
+ scope="background" if state.get("retry_scope") == "background" else "foreground"
474
+ )
475
+ retry_result = await self._retrier.ainvoke(
476
+ lambda: self._ainvoke_bound(request, config=config),
477
+ context=retry_ctx,
478
+ )
479
+ retry_event_commands = build_retry_event_commands(retry_result.events)
480
+ return retry_result.value, retry_event_commands
481
+ except Exception as primary_exc:
482
+ if self._fallback_model is None:
483
+ return _synthetic_api_error_model_response(primary_exc, self._name), retry_event_commands
484
+ logger.warning(
485
+ "Primary model ainvoke failed; retrying once with fallback_model",
486
+ exc_info=True,
487
+ )
488
+ req_fb = request.override(model=self._fallback_model)
489
+ try:
490
+ if self._retrier is None:
491
+ return await self._ainvoke_bound(req_fb, config=config), retry_event_commands
492
+ fb_result = await self._retrier.ainvoke(
493
+ lambda: self._ainvoke_bound(req_fb, config=config),
494
+ context=retry_ctx,
495
+ )
496
+ retry_event_commands.extend(build_retry_event_commands(fb_result.events))
497
+ return fb_result.value, retry_event_commands
498
+ except Exception as fb_exc:
499
+ return _synthetic_api_error_model_response(fb_exc, self._name), retry_event_commands
500
+
501
+ # ===== 退出逻辑与状态更新 =====
502
+
503
+ def _build_state_update(
504
+ self,
505
+ *,
506
+ state: dict,
507
+ model_response: Any,
508
+ retry_event_commands: list[Command],
509
+ mode: str,
510
+ ) -> dict:
511
+ """构建状态更新 + Command 列表。"""
512
+ last_ai_message = self._find_last_ai_message(model_response.result)
513
+ should_end, terminal_reason = determine_should_end(
514
+ state, last_ai_message, self._max_steps, self._max_turns
515
+ )
516
+ finish_reason = extract_finish_reason(last_ai_message)
517
+ self._log_stop_if_needed(
518
+ should_end=should_end,
519
+ terminal_reason=terminal_reason,
520
+ mode=mode,
521
+ step=state.get("step", 0) + 1,
522
+ finish_reason=finish_reason,
523
+ )
524
+ finish_reasons = self._append_finish_reason(state, finish_reason)
525
+ recovery_update = self._resolve_withhold_recovery_update(
526
+ state=state,
527
+ should_end=should_end,
528
+ terminal_reason=terminal_reason,
529
+ finish_reason=finish_reason,
530
+ )
531
+ state_update: Dict[str, Any] = {
532
+ "step": state.get("step", 0) + 1,
533
+ "should_end": recovery_update["should_end"],
534
+ "finish_reason": finish_reason,
535
+ "terminal_reason": recovery_update["terminal_reason"],
536
+ "withheld_error": recovery_update["withheld_error"],
537
+ "withhold_retry_count": recovery_update["withhold_retry_count"],
538
+ "max_output_tokens_override": recovery_update["max_output_tokens_override"],
539
+ "max_output_tokens_recovery_count": recovery_update["max_output_tokens_recovery_count"],
540
+ "transition": recovery_update["transition"],
541
+ "finish_reasons": finish_reasons,
542
+ }
543
+ base_state: Dict[str, Any] = {"messages": model_response.result}
544
+ if model_response.structured_response is not None:
545
+ base_state["structured_response"] = model_response.structured_response
546
+ state_update["structured_response"] = model_response.structured_response
547
+
548
+ return [*retry_event_commands, Command(update=base_state), Command(update=state_update)]
549
+
550
+ def _find_last_ai_message(self, messages: list[Any]) -> AIMessage | None:
551
+ for msg in reversed(messages):
552
+ if isinstance(msg, AIMessage):
553
+ return msg
554
+ return None
555
+
556
+ def _log_stop_if_needed(
557
+ self,
558
+ *,
559
+ should_end: bool,
560
+ terminal_reason: str | None,
561
+ mode: str,
562
+ step: int,
563
+ finish_reason: str | None,
564
+ ) -> None:
565
+ if not should_end:
566
+ return
567
+ logger.info(
568
+ "LangChain-AgentX agent stopping (%s) | terminal_reason=%s, step=%s, finish_reason=%s",
569
+ mode,
570
+ terminal_reason,
571
+ step,
572
+ finish_reason,
573
+ )
574
+
575
+ def _append_finish_reason(self, state: dict, finish_reason: str | None) -> list[str]:
576
+ finish_reasons = list(state.get("finish_reasons", []))
577
+ if finish_reason:
578
+ finish_reasons.append(finish_reason)
579
+ return finish_reasons
580
+
581
+ def _resolve_withhold_recovery_update(
582
+ self,
583
+ *,
584
+ state: dict,
585
+ should_end: bool,
586
+ terminal_reason: str | None,
587
+ finish_reason: str | None,
588
+ ) -> Dict[str, Any]:
589
+ withheld_error = state.get("withheld_error")
590
+ withhold_retry_count = int(state.get("withhold_retry_count", 0) or 0)
591
+ max_output_tokens_override = state.get("max_output_tokens_override")
592
+ max_output_tokens_recovery_count = int(state.get("max_output_tokens_recovery_count", 0) or 0)
593
+ can_default_withhold_retry = withhold_retry_count < self._max_withhold_retries
594
+ can_max_tokens_recovery_retry = (
595
+ terminal_reason == TERMINAL_REASON_MAX_TOKENS
596
+ and max_output_tokens_override is not None
597
+ and max_output_tokens_recovery_count < MAX_OUTPUT_TOKENS_RECOVERY_LIMIT
598
+ )
599
+ if should_end and terminal_reason in RECOVERABLE_TERMINAL_REASONS and (
600
+ can_default_withhold_retry or can_max_tokens_recovery_retry
601
+ ):
602
+ withheld_error = {"reason": terminal_reason, "finish_reason": finish_reason}
603
+ if can_default_withhold_retry:
604
+ withhold_retry_count += 1
605
+ return {
606
+ "should_end": False,
607
+ "terminal_reason": None,
608
+ "withheld_error": withheld_error,
609
+ "withhold_retry_count": withhold_retry_count,
610
+ "max_output_tokens_override": max_output_tokens_override,
611
+ "max_output_tokens_recovery_count": max_output_tokens_recovery_count,
612
+ "transition": {
613
+ "reason": TRANSITION_WITHHOLD_RECOVERY_RETRY,
614
+ "error_reason": withheld_error["reason"],
615
+ "attempt": withhold_retry_count,
616
+ },
617
+ }
618
+ if should_end and terminal_reason in RECOVERABLE_TERMINAL_REASONS:
619
+ return {
620
+ "should_end": should_end,
621
+ "terminal_reason": terminal_reason,
622
+ "withheld_error": None,
623
+ "withhold_retry_count": withhold_retry_count,
624
+ "max_output_tokens_override": max_output_tokens_override,
625
+ "max_output_tokens_recovery_count": max_output_tokens_recovery_count,
626
+ "transition": {
627
+ "reason": TRANSITION_WITHHOLD_RECOVERY_EXHAUSTED,
628
+ "error_reason": terminal_reason,
629
+ "attempt": withhold_retry_count,
630
+ },
631
+ }
632
+ if terminal_reason != TERMINAL_REASON_MAX_TOKENS:
633
+ max_output_tokens_override = None
634
+ max_output_tokens_recovery_count = 0
635
+ if not should_end:
636
+ withhold_retry_count = 0
637
+ return {
638
+ "should_end": should_end,
639
+ "terminal_reason": terminal_reason,
640
+ "withheld_error": None,
641
+ "withhold_retry_count": withhold_retry_count,
642
+ "max_output_tokens_override": max_output_tokens_override,
643
+ "max_output_tokens_recovery_count": max_output_tokens_recovery_count,
644
+ "transition": None,
645
+ }
646
+
647
+
648
+ __all__ = ["ModelNode"]