codeboarding 0.10.4__tar.gz → 0.12.0__tar.gz

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 (203) hide show
  1. {codeboarding-0.10.4/codeboarding.egg-info → codeboarding-0.12.0}/PKG-INFO +22 -10
  2. {codeboarding-0.10.4 → codeboarding-0.12.0}/PYPI.md +10 -8
  3. {codeboarding-0.10.4 → codeboarding-0.12.0}/README.md +9 -9
  4. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/agent.py +157 -146
  5. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/agent_responses.py +192 -28
  6. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/cluster_methods_mixin.py +53 -8
  7. codeboarding-0.12.0/agents/incremental_agent.py +787 -0
  8. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/llm_config.py +22 -9
  9. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/__init__.py +4 -0
  10. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/abstract_prompt_factory.py +8 -0
  11. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/claude_prompts.py +91 -34
  12. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/deepseek_prompts.py +77 -16
  13. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/gemini_flash_prompts.py +64 -28
  14. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/glm_prompts.py +80 -10
  15. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/gpt_prompts.py +68 -28
  16. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/kimi_prompts.py +71 -24
  17. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/prompt_factory.py +8 -0
  18. codeboarding-0.12.0/agents/retry.py +118 -0
  19. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/toolkit.py +0 -8
  20. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/validation.py +78 -30
  21. {codeboarding-0.10.4 → codeboarding-0.12.0/codeboarding.egg-info}/PKG-INFO +22 -10
  22. {codeboarding-0.10.4 → codeboarding-0.12.0}/codeboarding.egg-info/SOURCES.txt +24 -10
  23. {codeboarding-0.10.4 → codeboarding-0.12.0}/codeboarding.egg-info/requires.txt +11 -1
  24. {codeboarding-0.10.4 → codeboarding-0.12.0}/codeboarding.egg-info/top_level.txt +2 -1
  25. codeboarding-0.12.0/codeboarding_cli/__init__.py +1 -0
  26. codeboarding-0.12.0/codeboarding_cli/bootstrap.py +53 -0
  27. codeboarding-0.12.0/codeboarding_cli/commands/__init__.py +1 -0
  28. codeboarding-0.12.0/codeboarding_cli/commands/full_analysis.py +206 -0
  29. codeboarding-0.12.0/codeboarding_cli/commands/incremental_analysis.py +139 -0
  30. codeboarding-0.12.0/codeboarding_cli/commands/partial_analysis.py +68 -0
  31. codeboarding-0.12.0/codeboarding_workflows/__init__.py +15 -0
  32. codeboarding-0.12.0/codeboarding_workflows/analysis.py +238 -0
  33. codeboarding-0.12.0/codeboarding_workflows/orchestration.py +48 -0
  34. codeboarding-0.12.0/codeboarding_workflows/rendering.py +92 -0
  35. codeboarding-0.12.0/codeboarding_workflows/sources/__init__.py +12 -0
  36. codeboarding-0.12.0/codeboarding_workflows/sources/local.py +23 -0
  37. codeboarding-0.12.0/codeboarding_workflows/sources/remote.py +71 -0
  38. {codeboarding-0.10.4 → codeboarding-0.12.0}/core/__init__.py +2 -1
  39. {codeboarding-0.10.4 → codeboarding-0.12.0}/core/protocols.py +2 -1
  40. codeboarding-0.12.0/diagram_analysis/__init__.py +22 -0
  41. {codeboarding-0.10.4 → codeboarding-0.12.0}/diagram_analysis/analysis_json.py +6 -1
  42. codeboarding-0.12.0/diagram_analysis/cluster_delta.py +455 -0
  43. codeboarding-0.12.0/diagram_analysis/cluster_snapshot.py +101 -0
  44. codeboarding-0.12.0/diagram_analysis/diagram_generator.py +735 -0
  45. codeboarding-0.12.0/diagram_analysis/exceptions.py +43 -0
  46. {codeboarding-0.10.4 → codeboarding-0.12.0}/diagram_analysis/io_utils.py +145 -34
  47. codeboarding-0.12.0/diagram_analysis/run_mode.py +10 -0
  48. codeboarding-0.12.0/github_action.py +130 -0
  49. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/cohesion.py +6 -0
  50. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/runner.py +9 -3
  51. {codeboarding-0.10.4 → codeboarding-0.12.0}/install.py +1 -1
  52. {codeboarding-0.10.4 → codeboarding-0.12.0}/logging_config.py +1 -1
  53. codeboarding-0.12.0/main.py +93 -0
  54. {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/paths.py +4 -0
  55. {codeboarding-0.10.4 → codeboarding-0.12.0}/pyproject.toml +44 -4
  56. {codeboarding-0.10.4 → codeboarding-0.12.0}/repo_utils/__init__.py +2 -5
  57. codeboarding-0.12.0/repo_utils/change_detector.py +315 -0
  58. codeboarding-0.12.0/repo_utils/diff_parser.py +290 -0
  59. codeboarding-0.12.0/repo_utils/git_ops.py +319 -0
  60. {codeboarding-0.10.4 → codeboarding-0.12.0}/repo_utils/ignore.py +33 -0
  61. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/__init__.py +308 -266
  62. codeboarding-0.12.0/static_analyzer/analysis_cache.py +475 -0
  63. codeboarding-0.12.0/static_analyzer/analysis_result.py +273 -0
  64. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/cluster_helpers.py +52 -30
  65. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/constants.py +29 -0
  66. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/csharp_adapter.py +3 -3
  67. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/go_adapter.py +3 -2
  68. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/java_adapter.py +3 -3
  69. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/php_adapter.py +3 -3
  70. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/python_adapter.py +3 -2
  71. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/rust_adapter.py +5 -4
  72. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/typescript_adapter.py +5 -4
  73. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/language_adapter.py +15 -2
  74. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/graph.py +110 -22
  75. codeboarding-0.12.0/static_analyzer/incremental_orchestrator.py +125 -0
  76. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/java_utils.py +8 -10
  77. codeboarding-0.12.0/static_analyzer/language_results.py +128 -0
  78. codeboarding-0.12.0/static_analyzer/leiden_utils.py +103 -0
  79. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/reference_resolve_mixin.py +8 -4
  80. codeboarding-0.12.0/static_analyzer/typescript_config_scanner.py +235 -0
  81. codeboarding-0.12.0/tests/test_cli_parser.py +70 -0
  82. {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_github_action.py +23 -11
  83. codeboarding-0.12.0/tests/test_main.py +578 -0
  84. {codeboarding-0.10.4 → codeboarding-0.12.0}/user_config.py +2 -2
  85. {codeboarding-0.10.4 → codeboarding-0.12.0}/utils.py +31 -1
  86. codeboarding-0.10.4/agents/tools/read_git_diff.py +0 -131
  87. codeboarding-0.10.4/diagram_analysis/__init__.py +0 -3
  88. codeboarding-0.10.4/diagram_analysis/diagram_generator.py +0 -396
  89. codeboarding-0.10.4/diagram_analysis/incremental_types.py +0 -70
  90. codeboarding-0.10.4/diagram_analysis/incremental_updater.py +0 -400
  91. codeboarding-0.10.4/duckdb_crud.py +0 -125
  92. codeboarding-0.10.4/github_action.py +0 -173
  93. codeboarding-0.10.4/health/constants.py +0 -19
  94. codeboarding-0.10.4/health_main.py +0 -151
  95. codeboarding-0.10.4/main.py +0 -567
  96. codeboarding-0.10.4/repo_utils/change_detector.py +0 -294
  97. codeboarding-0.10.4/repo_utils/git_diff.py +0 -74
  98. codeboarding-0.10.4/repo_utils/method_diff.py +0 -162
  99. codeboarding-0.10.4/static_analyzer/analysis_cache.py +0 -761
  100. codeboarding-0.10.4/static_analyzer/analysis_result.py +0 -450
  101. codeboarding-0.10.4/static_analyzer/cluster_change_analyzer.py +0 -391
  102. codeboarding-0.10.4/static_analyzer/git_diff_analyzer.py +0 -224
  103. codeboarding-0.10.4/static_analyzer/incremental_orchestrator.py +0 -644
  104. codeboarding-0.10.4/static_analyzer/typescript_config_scanner.py +0 -54
  105. codeboarding-0.10.4/tests/test_main.py +0 -511
  106. {codeboarding-0.10.4 → codeboarding-0.12.0}/LICENSE +0 -0
  107. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/__init__.py +0 -0
  108. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/abstraction_agent.py +0 -0
  109. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/change_status.py +0 -0
  110. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/cluster_budget.py +0 -0
  111. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/constants.py +0 -0
  112. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/dependency_discovery.py +0 -0
  113. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/details_agent.py +0 -0
  114. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/meta_agent.py +0 -0
  115. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/model_capabilities.py +0 -0
  116. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/planner_agent.py +0 -0
  117. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/__init__.py +0 -0
  118. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/base.py +0 -0
  119. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/get_external_deps.py +0 -0
  120. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/get_method_invocations.py +0 -0
  121. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_cfg.py +0 -0
  122. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_docs.py +0 -0
  123. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_file.py +0 -0
  124. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_file_structure.py +0 -0
  125. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_packages.py +0 -0
  126. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_source.py +0 -0
  127. {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_structure.py +0 -0
  128. {codeboarding-0.10.4 → codeboarding-0.12.0}/caching/__init__.py +0 -0
  129. {codeboarding-0.10.4 → codeboarding-0.12.0}/caching/cache.py +0 -0
  130. {codeboarding-0.10.4 → codeboarding-0.12.0}/caching/details_cache.py +0 -0
  131. {codeboarding-0.10.4 → codeboarding-0.12.0}/caching/meta_cache.py +0 -0
  132. {codeboarding-0.10.4 → codeboarding-0.12.0}/codeboarding.egg-info/dependency_links.txt +0 -0
  133. {codeboarding-0.10.4 → codeboarding-0.12.0}/codeboarding.egg-info/entry_points.txt +0 -0
  134. {codeboarding-0.10.4 → codeboarding-0.12.0}/constants.py +0 -0
  135. {codeboarding-0.10.4 → codeboarding-0.12.0}/core/plugin_loader.py +0 -0
  136. {codeboarding-0.10.4 → codeboarding-0.12.0}/core/registry.py +0 -0
  137. {codeboarding-0.10.4 → codeboarding-0.12.0}/diagram_analysis/file_coverage.py +1 -1
  138. {codeboarding-0.10.4 → codeboarding-0.12.0}/diagram_analysis/run_context.py +0 -0
  139. {codeboarding-0.10.4 → codeboarding-0.12.0}/diagram_analysis/version.py +0 -0
  140. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/__init__.py +0 -0
  141. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/__init__.py +0 -0
  142. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/circular_deps.py +0 -0
  143. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/coupling.py +0 -0
  144. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/function_size.py +0 -0
  145. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/god_class.py +0 -0
  146. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/inheritance.py +0 -0
  147. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/instability.py +0 -0
  148. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/unused_code_diagnostics.py +0 -0
  149. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/config.py +0 -0
  150. {codeboarding-0.10.4 → codeboarding-0.12.0}/health/models.py +0 -0
  151. {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/__init__.py +0 -0
  152. {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/callbacks.py +0 -0
  153. {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/context.py +0 -0
  154. {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/mixin.py +0 -0
  155. {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/stats.py +0 -0
  156. {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/writers.py +0 -0
  157. {codeboarding-0.10.4 → codeboarding-0.12.0}/output_generators/__init__.py +0 -0
  158. {codeboarding-0.10.4 → codeboarding-0.12.0}/output_generators/html.py +0 -0
  159. {codeboarding-0.10.4 → codeboarding-0.12.0}/output_generators/html_template.py +0 -0
  160. {codeboarding-0.10.4 → codeboarding-0.12.0}/output_generators/markdown.py +0 -0
  161. {codeboarding-0.10.4 → codeboarding-0.12.0}/output_generators/mdx.py +0 -0
  162. {codeboarding-0.10.4 → codeboarding-0.12.0}/output_generators/sphinx.py +0 -0
  163. {codeboarding-0.10.4 → codeboarding-0.12.0}/repo_utils/errors.py +0 -0
  164. {codeboarding-0.10.4 → codeboarding-0.12.0}/setup.cfg +0 -0
  165. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/cfg_skip_planner.py +0 -0
  166. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/cluster_relations.py +0 -0
  167. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/csharp_config_scanner.py +0 -0
  168. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/__init__.py +0 -0
  169. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/__init__.py +0 -0
  170. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/call_graph_builder.py +0 -0
  171. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/edge_build_context.py +0 -0
  172. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/edge_builder.py +0 -0
  173. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/hierarchy_builder.py +0 -0
  174. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/lsp_client.py +0 -0
  175. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/lsp_constants.py +0 -0
  176. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/models.py +0 -0
  177. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/progress.py +0 -0
  178. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/protocols.py +0 -0
  179. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/result_converter.py +0 -0
  180. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/source_inspector.py +0 -0
  181. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/symbol_table.py +0 -0
  182. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/utils.py +0 -0
  183. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/java_config_scanner.py +0 -0
  184. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/lsp_client/__init__.py +0 -0
  185. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/lsp_client/diagnostics.py +0 -0
  186. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/node.py +0 -0
  187. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/programming_language.py +0 -0
  188. {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/scanner.py +0 -0
  189. {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_install.py +0 -0
  190. {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_logging_config.py +0 -0
  191. {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_pyproject_packages.py +0 -0
  192. {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_registry_coverage.py +0 -0
  193. {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_tool_registry.py +0 -0
  194. {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_user_config.py +0 -0
  195. {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_vscode_constants.py +0 -0
  196. {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_windows_compatibility.py +0 -0
  197. {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_windows_encoding.py +0 -0
  198. {codeboarding-0.10.4 → codeboarding-0.12.0}/tool_registry/__init__.py +0 -0
  199. {codeboarding-0.10.4 → codeboarding-0.12.0}/tool_registry/installers.py +0 -0
  200. {codeboarding-0.10.4 → codeboarding-0.12.0}/tool_registry/manifest.py +0 -0
  201. {codeboarding-0.10.4 → codeboarding-0.12.0}/tool_registry/paths.py +0 -0
  202. {codeboarding-0.10.4 → codeboarding-0.12.0}/tool_registry/registry.py +0 -0
  203. {codeboarding-0.10.4 → codeboarding-0.12.0}/vscode_constants.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.10.4
3
+ Version: 0.12.0
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -18,12 +18,12 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: docker>=7.1
20
20
  Requires-Dist: dotenv>=0.9
21
- Requires-Dist: duckdb>=1.3
22
21
  Requires-Dist: dulwich>=0.22
23
22
  Requires-Dist: fastapi>=0.115
24
23
  Requires-Dist: filelock>=3.12
25
24
  Requires-Dist: gitpython>=3.1
26
25
  Requires-Dist: google-api-core>=2.10
26
+ Requires-Dist: jsonpatch>=1.33
27
27
  Requires-Dist: jsonschema>=4.25
28
28
  Requires-Dist: langchain>=1.2
29
29
  Requires-Dist: langchain-anthropic>=1.3
@@ -33,6 +33,7 @@ Requires-Dist: langchain-community>=0.4
33
33
  Requires-Dist: langchain-google-genai>=3.1
34
34
  Requires-Dist: langchain-ollama>=1.0
35
35
  Requires-Dist: langchain-openai>=1.1
36
+ Requires-Dist: leidenalg>=0.10
36
37
  Requires-Dist: markdown>=3.8
37
38
  Requires-Dist: markdown-it-py>=3.0
38
39
  Requires-Dist: markitdown>=0.1
@@ -42,6 +43,15 @@ Requires-Dist: pathspec>=0.12
42
43
  Requires-Dist: pyyaml>=6.0
43
44
  Requires-Dist: regex>=2024.11
44
45
  Requires-Dist: rich>=12.6
46
+ Requires-Dist: tree-sitter>=0.23
47
+ Requires-Dist: tree-sitter-c-sharp>=0.23
48
+ Requires-Dist: tree-sitter-go>=0.23
49
+ Requires-Dist: tree-sitter-java>=0.23
50
+ Requires-Dist: tree-sitter-javascript>=0.23
51
+ Requires-Dist: tree-sitter-php>=0.23
52
+ Requires-Dist: tree-sitter-python>=0.23
53
+ Requires-Dist: tree-sitter-rust>=0.23
54
+ Requires-Dist: tree-sitter-typescript>=0.23
45
55
  Requires-Dist: trustcall>=0.0.39
46
56
  Requires-Dist: uvicorn>=0.23
47
57
  Provides-Extra: dev
@@ -110,10 +120,10 @@ codeboarding-setup
110
120
 
111
121
  ```bash
112
122
  # Analyze a local repository (output goes to /path/to/repo/.codeboarding/)
113
- codeboarding --local /path/to/repo
123
+ codeboarding full --local /path/to/repo
114
124
 
115
125
  # Analyze a remote GitHub repository (cloned to cwd/repo_name/, output to cwd/repo_name/.codeboarding/)
116
- codeboarding https://github.com/user/repo
126
+ codeboarding full https://github.com/user/repo
117
127
  ```
118
128
 
119
129
  ### Python API
@@ -188,19 +198,21 @@ Shell environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.) always
188
198
  ## CLI Reference
189
199
 
190
200
  ```
191
- codeboarding [REPO_URL ...] # remote: clone + analyze
192
- codeboarding --local PATH # local: analyze in-place
201
+ codeboarding full [REPO_URL ...] # remote: clone + analyze
202
+ codeboarding full --local PATH # local: analyze in-place
203
+ codeboarding incremental --local PATH # re-analyze only changed parts
204
+ codeboarding partial --local PATH --component-id ID # update one component
193
205
  ```
194
206
 
195
207
  | Option | Description |
196
208
  |---|---|
197
209
  | `--local PATH` | Analyze a local repository (output: `PATH/.codeboarding/`) |
198
210
  | `--depth-level INT` | Diagram depth (default: 1) |
199
- | `--incremental` | Smart incremental update (only re-analyze changed files) |
200
- | `--full` | Force full reanalysis, skip incremental detection |
201
- | `--partial-component-id ID` | Update a single component by its ID |
211
+ | `--force` | (full only) Force full reanalysis, skip cached static analysis |
212
+ | `--base-ref REF` / `--target-ref REF` | (incremental only) Git refs to diff |
213
+ | `--component-id ID` | (partial only) ID of the component to update |
202
214
  | `--binary-location PATH` | Custom path to language server binaries (overrides `~/.codeboarding/servers/`) |
203
- | `--upload` | Upload results to GeneratedOnBoardings repo (remote only) |
215
+ | `--upload` | (full, remote only) Upload results to GeneratedOnBoardings repo |
204
216
  | `--enable-monitoring` | Enable run monitoring |
205
217
 
206
218
  ---
@@ -50,10 +50,10 @@ codeboarding-setup
50
50
 
51
51
  ```bash
52
52
  # Analyze a local repository (output goes to /path/to/repo/.codeboarding/)
53
- codeboarding --local /path/to/repo
53
+ codeboarding full --local /path/to/repo
54
54
 
55
55
  # Analyze a remote GitHub repository (cloned to cwd/repo_name/, output to cwd/repo_name/.codeboarding/)
56
- codeboarding https://github.com/user/repo
56
+ codeboarding full https://github.com/user/repo
57
57
  ```
58
58
 
59
59
  ### Python API
@@ -128,19 +128,21 @@ Shell environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.) always
128
128
  ## CLI Reference
129
129
 
130
130
  ```
131
- codeboarding [REPO_URL ...] # remote: clone + analyze
132
- codeboarding --local PATH # local: analyze in-place
131
+ codeboarding full [REPO_URL ...] # remote: clone + analyze
132
+ codeboarding full --local PATH # local: analyze in-place
133
+ codeboarding incremental --local PATH # re-analyze only changed parts
134
+ codeboarding partial --local PATH --component-id ID # update one component
133
135
  ```
134
136
 
135
137
  | Option | Description |
136
138
  |---|---|
137
139
  | `--local PATH` | Analyze a local repository (output: `PATH/.codeboarding/`) |
138
140
  | `--depth-level INT` | Diagram depth (default: 1) |
139
- | `--incremental` | Smart incremental update (only re-analyze changed files) |
140
- | `--full` | Force full reanalysis, skip incremental detection |
141
- | `--partial-component-id ID` | Update a single component by its ID |
141
+ | `--force` | (full only) Force full reanalysis, skip cached static analysis |
142
+ | `--base-ref REF` / `--target-ref REF` | (incremental only) Git refs to diff |
143
+ | `--component-id ID` | (partial only) ID of the component to update |
142
144
  | `--binary-location PATH` | Custom path to language server binaries (overrides `~/.codeboarding/servers/`) |
143
- | `--upload` | Upload results to GeneratedOnBoardings repo (remote only) |
145
+ | `--upload` | (full, remote only) Upload results to GeneratedOnBoardings repo |
144
146
  | `--enable-monitoring` | Enable run monitoring |
145
147
 
146
148
  ---
@@ -70,7 +70,7 @@ For a deeper architecture walkthrough, see [`.codeboarding/overview.md`](.codebo
70
70
  uv sync --frozen
71
71
  source .venv/bin/activate # On Windows: .venv\Scripts\activate
72
72
  python install.py
73
- python main.py --local /path/to/repo
73
+ python main.py full --local /path/to/repo
74
74
  ```
75
75
 
76
76
  ### Use the packaged CLI
@@ -80,7 +80,7 @@ Requires **Python 3.12 or 3.13**. The recommended install method is [pipx](https
80
80
  ```bash
81
81
  pipx install codeboarding --python python3.12
82
82
  codeboarding-setup
83
- codeboarding --local /path/to/repo
83
+ codeboarding full --local /path/to/repo
84
84
  ```
85
85
 
86
86
  Or, if you prefer pip, install into a virtual environment (not the global Python):
@@ -88,7 +88,7 @@ Or, if you prefer pip, install into a virtual environment (not the global Python
88
88
  ```bash
89
89
  pip install codeboarding
90
90
  codeboarding-setup
91
- codeboarding --local /path/to/repo
91
+ codeboarding full --local /path/to/repo
92
92
  ```
93
93
 
94
94
  Output is written to `/path/to/repo/.codeboarding/`.
@@ -120,19 +120,19 @@ Shell environment variables such as `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOG
120
120
 
121
121
  ```bash
122
122
  # Analyze a local repository
123
- python main.py --local ./my-project
123
+ python main.py full --local ./my-project
124
124
 
125
125
  # Increase diagram depth
126
- python main.py --local ./my-project --depth-level 2
126
+ python main.py full --local ./my-project --depth-level 2
127
127
 
128
128
  # Re-analyze only changed parts when possible
129
- python main.py --local ./my-project --incremental
129
+ python main.py incremental --local ./my-project
130
130
 
131
131
  # Update a single component by ID
132
- python main.py --local ./my-project --partial-component-id "1.2"
132
+ python main.py partial --local ./my-project --component-id "1.2"
133
133
 
134
134
  # Analyze a remote GitHub repository
135
- python main.py https://github.com/pytorch/pytorch
135
+ python main.py full https://github.com/pytorch/pytorch
136
136
  ```
137
137
 
138
138
  ## Where to use it
@@ -143,7 +143,7 @@ python main.py https://github.com/pytorch/pytorch
143
143
 
144
144
  ## Supported stack
145
145
 
146
- - Languages: Python, TypeScript, JavaScript, Java, Go, PHP, Rust.
146
+ - Languages: Python, TypeScript, JavaScript, Java, Go, PHP, Rust, C#.
147
147
  - LLM providers: OpenAI, Anthropic, Google, Vercel AI Gateway, AWS Bedrock, Ollama, OpenRouter, and more.
148
148
 
149
149
  ## Examples
@@ -1,12 +1,11 @@
1
1
  import json
2
2
  import logging
3
- import time
4
3
  from pathlib import Path
5
4
 
6
5
  from google.api_core.exceptions import ResourceExhausted
7
6
  from langchain_core.exceptions import OutputParserException
8
7
  from langchain_core.language_models import BaseChatModel
9
- from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
8
+ from langchain_core.messages import SystemMessage, HumanMessage, AIMessage, ToolMessage
10
9
  from langchain_core.output_parsers import PydanticOutputParser
11
10
  from langchain_core.prompts import PromptTemplate
12
11
  from langchain.agents import create_agent
@@ -15,11 +14,13 @@ from pydantic import ValidationError
15
14
  from trustcall import create_extractor
16
15
 
17
16
  from agents.prompts import get_validation_feedback_message
17
+ from agents.retry import RetryAction, RetryDecision, default_backoff, with_retries
18
18
  from agents.tools.base import RepoContext
19
19
  from agents.tools.toolkit import CodeBoardingToolkit
20
20
  from agents.validation import ValidationResult, score_validation_results, VALIDATOR_WEIGHTS, DEFAULT_VALIDATOR_WEIGHT
21
21
  from monitoring.mixin import MonitoringMixin
22
22
  from repo_utils.ignore import RepoIgnoreManager
23
+ from agents.agent_responses import LLMBaseModel
23
24
  from agents.llm_config import MONITORING_CALLBACK
24
25
  from static_analyzer.analysis_result import StaticAnalysisResults
25
26
  from static_analyzer.reference_resolve_mixin import ReferenceResolverMixin
@@ -43,10 +44,10 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
43
44
  ReferenceResolverMixin.__init__(self, repo_dir, static_analysis)
44
45
  MonitoringMixin.__init__(self)
45
46
  self.parsing_llm = parsing_llm
47
+ self.agent_llm = agent_llm
46
48
  self.repo_dir = repo_dir
47
49
  self.ignore_manager = RepoIgnoreManager(repo_dir)
48
50
 
49
- # Initialize the professional toolkit
50
51
  context = RepoContext(repo_dir=repo_dir, ignore_manager=self.ignore_manager, static_analysis=static_analysis)
51
52
  self.toolkit = CodeBoardingToolkit(context=context)
52
53
 
@@ -96,86 +97,69 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
96
97
  def _invoke(self, prompt, callbacks: list | None = None) -> str:
97
98
  """Unified agent invocation method with timeout and exponential backoff.
98
99
 
99
- Uses exponential backoff based on total attempts, with different multipliers
100
- for different error types. This ensures backoff increases appropriately even
101
- when errors alternate between types.
100
+ Classification applied per exception:
101
+ - ``TimeoutError``: backoff ``min(10·2^n, 120)``, raise on exhaustion.
102
+ - ``ResourceExhausted``: backoff ``min(30·2^n, 300)``, raise on exhaustion.
103
+ - ``status_code == 404``: raise immediately (retired model ID, etc.).
104
+ - Other exceptions: backoff ``min(10·2^n, 120)``, return fallback string
105
+ on exhaustion (non-raising — callers treat the fallback as a failed run).
102
106
  """
103
- max_retries = 5
104
-
105
- for attempt in range(max_retries):
107
+ max_attempts = 5
108
+ # Counter captured by the closure so we can vary the per-attempt timeout
109
+ # without reaching into the retry helper.
110
+ attempt_counter = [0]
111
+
112
+ def call_once() -> str:
113
+ attempt = attempt_counter[0]
114
+ attempt_counter[0] += 1
106
115
  timeout_seconds = 300 if attempt == 0 else 600
107
- try:
108
- callback_list = callbacks or []
109
- # Always append monitoring callback - logging config controls output
110
- callback_list.append(MONITORING_CALLBACK)
111
- callback_list.append(self.agent_monitoring_callback)
112
-
113
- logger.info(
114
- f"Starting agent.invoke() [attempt {attempt + 1}/{max_retries}] with prompt length: {len(prompt)}, timeout: {timeout_seconds}s"
115
- )
116
-
117
- response = self._invoke_with_timeout(
118
- timeout_seconds=timeout_seconds, callback_list=callback_list, prompt=prompt
119
- )
120
-
121
- logger.info(
122
- f"Completed agent.invoke() - message count: {len(response['messages'])}, last message type: {type(response['messages'][-1])}"
116
+ callback_list = (callbacks or []) + [MONITORING_CALLBACK, self.agent_monitoring_callback]
117
+ logger.info(
118
+ f"Starting agent.invoke() [attempt {attempt + 1}/{max_attempts}] with prompt length: {len(prompt)}, timeout: {timeout_seconds}s"
119
+ )
120
+ response = self._invoke_with_timeout(
121
+ timeout_seconds=timeout_seconds, callback_list=callback_list, prompt=prompt
122
+ )
123
+ logger.info(
124
+ f"Completed agent.invoke() - message count: {len(response['messages'])}, last message type: {type(response['messages'][-1])}"
125
+ )
126
+ agent_response = response["messages"][-1]
127
+ assert isinstance(agent_response, AIMessage), f"Expected AIMessage, but got {type(agent_response)}"
128
+ if isinstance(agent_response.content, str):
129
+ return agent_response.content
130
+ if isinstance(agent_response.content, list):
131
+ return "".join(str(m) if not isinstance(m, str) else m for m in agent_response.content)
132
+ return "" # unreachable for AIMessage but satisfies typing
133
+
134
+ def classify(exc: Exception, attempt: int) -> RetryDecision:
135
+ if getattr(exc, "status_code", None) == 404:
136
+ logger.error(f"Permanent HTTP 404 — not retrying: {type(exc).__name__}: {exc}")
137
+ return RetryDecision(action=RetryAction.GIVE_UP)
138
+ if isinstance(exc, ResourceExhausted):
139
+ return RetryDecision(
140
+ action=RetryAction.RETRY,
141
+ backoff_s=default_backoff(attempt, initial_s=30.0, multiplier=2.0, max_s=300.0),
123
142
  )
143
+ # TimeoutError + generic Exception share the same backoff.
144
+ return RetryDecision(
145
+ action=RetryAction.RETRY,
146
+ backoff_s=default_backoff(attempt, initial_s=10.0, multiplier=2.0, max_s=120.0),
147
+ )
124
148
 
125
- agent_response = response["messages"][-1]
126
- assert isinstance(agent_response, AIMessage), f"Expected AIMessage, but got {type(agent_response)}"
127
- if isinstance(agent_response.content, str):
128
- return agent_response.content
129
- if isinstance(agent_response.content, list):
130
- return "".join(
131
- [
132
- str(message) if not isinstance(message, str) else message
133
- for message in agent_response.content
134
- ]
135
- )
136
-
137
- except TimeoutError as e:
138
- if attempt < max_retries - 1:
139
- # Exponential backoff: 10s * 2^attempt (10s, 20s, 40s, 80s)
140
- delay = min(10 * (2**attempt), 120)
141
- logger.warning(
142
- f"Agent invocation timed out after {timeout_seconds}s, retrying in {delay}s... (attempt {attempt + 1}/{max_retries})"
143
- )
144
- time.sleep(delay)
145
- else:
146
- logger.error(f"Agent invocation timed out after {timeout_seconds}s on final attempt")
147
- raise
148
-
149
- except ResourceExhausted as e:
150
- if attempt < max_retries - 1:
151
- # Longer backoff for rate limits: 30s * 2^attempt (30s, 60s, 120s, 240s)
152
- delay = min(30 * (2**attempt), 300)
153
- logger.warning(
154
- f"ResourceExhausted (rate limit): {e}\n"
155
- f"Retrying in {delay}s... (attempt {attempt + 1}/{max_retries})"
156
- )
157
- time.sleep(delay)
158
- else:
159
- logger.error(f"Max retries ({max_retries}) reached. ResourceExhausted: {e}")
160
- raise
161
-
162
- except Exception as e:
163
- # HTTP 404 (e.g. retired model ID) is permanent — retrying won't help.
164
- if getattr(e, "status_code", None) == 404:
165
- logger.error(f"Permanent HTTP 404 — not retrying: {type(e).__name__}: {e}")
166
- raise
167
-
168
- # Other errors (network, parsing, etc.) get standard exponential backoff
169
- if attempt < max_retries - 1:
170
- delay = min(10 * (2**attempt), 120)
171
- logger.warning(
172
- f"Agent error: {type(e).__name__}: {e}, retrying in {delay}s... (attempt {attempt + 1}/{max_retries})"
173
- )
174
- time.sleep(delay)
175
- # On final attempt, fall through to return error message below
176
-
177
- logger.error("Max retries reached. Failed to get response from the agent.")
178
- return "Could not get response from the agent."
149
+ def on_exhausted(exc: Exception) -> str:
150
+ # Typed exceptions surface the original error; only generic falls through
151
+ # to the historic fallback string that callers have long relied on.
152
+ if isinstance(exc, (TimeoutError, ResourceExhausted)):
153
+ raise exc
154
+ return "Could not get response from the agent."
155
+
156
+ return with_retries(
157
+ call_once,
158
+ max_attempts=max_attempts,
159
+ classify=classify,
160
+ on_exhausted=on_exhausted,
161
+ log_prefix="Agent invocation",
162
+ )
179
163
 
180
164
  def _invoke_with_timeout(self, timeout_seconds: int, callback_list: list, prompt: str):
181
165
  """Invoke agent with a timeout using threading."""
@@ -217,10 +201,10 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
217
201
  except Empty:
218
202
  raise RuntimeError("Agent invocation completed but no result was returned")
219
203
 
220
- def _parse_invoke(self, prompt: str, type: type):
204
+ def _parse_invoke(self, prompt: str, type: type, include_hidden: bool = False):
221
205
  response = self._invoke(prompt)
222
206
  assert isinstance(response, str), f"Expected a string as response type got {response}"
223
- return self._parse_response(prompt, response, type)
207
+ return self._parse_response(prompt, response, type, include_hidden=include_hidden)
224
208
 
225
209
  def _score_result(self, result, validators: list, context) -> tuple[float, list[tuple[float, str]]]:
226
210
  """Run all validators on a result and return (score, prioritized_feedback).
@@ -250,7 +234,13 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
250
234
  return score, weighted_feedback
251
235
 
252
236
  def _validation_invoke(
253
- self, prompt: str, return_type: type, validators: list, context, max_validation_attempts: int = 1
237
+ self,
238
+ prompt: str,
239
+ return_type: type,
240
+ validators: list,
241
+ context,
242
+ max_validation_attempts: int = 1,
243
+ include_hidden: bool = False,
254
244
  ):
255
245
  """
256
246
  Invoke LLM with validation, feedback loop, and best-of-N selection.
@@ -278,7 +268,12 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
278
268
  # Compute the maximum possible score so we can detect a perfect result
279
269
  max_possible_score = sum(VALIDATOR_WEIGHTS.get(v.__name__, DEFAULT_VALIDATOR_WEIGHT) for v in validators)
280
270
 
281
- result = self._parse_invoke(prompt, return_type)
271
+ result = self._parse_invoke(prompt, return_type, include_hidden=include_hidden)
272
+ logger.info(
273
+ "[Validation] Parsed %s: %s",
274
+ return_type.__name__,
275
+ result.llm_str()[:500],
276
+ )
282
277
 
283
278
  # Track the best candidate across all attempts
284
279
  best_result = result
@@ -331,79 +326,74 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
331
326
  f"[Validation] Preparing attempt {attempt + 1}/{max_validation_attempts} "
332
327
  f"with {len(weighted_feedback)} feedback items"
333
328
  )
334
- result = self._parse_invoke(feedback_prompt, return_type)
329
+ result = self._parse_invoke(feedback_prompt, return_type, include_hidden=include_hidden)
335
330
 
336
331
  return best_result
337
332
 
338
- def _parse_response(self, prompt, response, return_type, max_retries=5, attempt=0):
339
- if attempt >= max_retries:
340
- logger.error(f"Max retries ({max_retries}) reached for parsing response: {response}")
341
- raise Exception(f"Max retries reached for parsing response: {response}")
342
-
343
- extractor = create_extractor(self.parsing_llm, tools=[return_type], tool_choice=return_type.__name__)
333
+ def _parse_response(self, prompt, response, return_type, max_retries=5, attempt=0, include_hidden: bool = False):
344
334
  if response is None or response.strip() == "":
345
335
  logger.error(f"Empty response for prompt: {prompt}")
346
- try:
347
- result = extractor.invoke(
348
- return_type.extractor_str() + response,
349
- config={"callbacks": [MONITORING_CALLBACK, self.agent_monitoring_callback]},
336
+
337
+ if include_hidden and issubclass(return_type, LLMBaseModel):
338
+ schema = return_type.model_json_schema(include_hidden=True)
339
+ parser = PydanticOutputParser(pydantic_object=return_type)
340
+ format_instructions = (
341
+ f"The output should be formatted as a JSON instance that conforms to the JSON schema below.\n"
342
+ f"Here is the output schema:\n```json\n{json.dumps(schema, indent=2)}\n```"
350
343
  )
351
- if "responses" in result and len(result["responses"]) != 0:
352
- return return_type.model_validate(result["responses"][0])
353
- if "messages" in result and len(result["messages"]) != 0:
354
- message = result["messages"][0].content
355
- parser = PydanticOutputParser(pydantic_object=return_type)
356
- if not message:
357
- raise EmptyExtractorMessageError("Extractor returned empty message content")
358
- return self._try_parse(message, parser)
344
+ else:
359
345
  parser = PydanticOutputParser(pydantic_object=return_type)
360
- return self._try_parse(response, parser)
361
- except EmptyExtractorMessageError as e:
362
- logger.warning(f"{e} (attempt {attempt + 1}/{max_retries})")
363
- return self._parse_response(prompt, response, return_type, max_retries, attempt + 1)
364
- except AttributeError as e:
365
- # Workaround for trustcall bug: https://github.com/hinthornw/trustcall/issues/47
366
- # 'ExtractionState' object has no attribute 'tool_call_id' occurs during validation retry
367
- if "tool_call_id" in str(e):
368
- logger.warning(f"Trustcall bug encountered, falling back to Pydantic parser: {e}")
369
- parser = PydanticOutputParser(pydantic_object=return_type)
370
- return self._try_parse(response, parser)
371
- raise
372
- except IndexError as e:
373
- # try to parse with the json parser if possible
374
- logger.warning(f"IndexError while parsing response (attempt {attempt + 1}/{max_retries}): {e}")
375
- return self._parse_response(prompt, response, return_type, max_retries, attempt + 1)
376
- except (json.JSONDecodeError, ValueError) as e:
377
- logger.warning(f"Parse error (attempt {attempt + 1}/{max_retries}): {e}")
378
- return self._parse_response(prompt, response, return_type, max_retries, attempt + 1)
379
- except ResourceExhausted as e:
380
- # Parsing uses exponential backoff for rate limits
381
- if attempt < max_retries - 1:
382
- # Exponential backoff: 30s * 2^attempt, capped at 300s
383
- delay = min(30 * (2**attempt), 300)
384
- logger.warning(
385
- f"ResourceExhausted during parsing (rate limit): {e}\n"
386
- f"Retrying in {delay}s... (attempt {attempt + 1}/{max_retries})"
346
+ format_instructions = parser.get_format_instructions()
347
+
348
+ def call_once():
349
+ try:
350
+ result = self._structured_parse(response, parser, format_instructions=format_instructions)
351
+ logger.debug("[parse_response] structured_parse succeeded for %s", return_type.__name__)
352
+ return result
353
+ except Exception as e:
354
+ logger.warning("[parse_response] structured_parse failed for %s: %s", return_type.__name__, e)
355
+ return self._extractor_parse(response, return_type, parser, include_hidden=include_hidden)
356
+
357
+ def classify(exc: Exception, attempt: int) -> RetryDecision:
358
+ if isinstance(exc, ResourceExhausted):
359
+ return RetryDecision(
360
+ action=RetryAction.RETRY,
361
+ backoff_s=default_backoff(attempt, initial_s=30.0, multiplier=2.0, max_s=300.0),
387
362
  )
388
- time.sleep(delay)
389
- return self._parse_response(prompt, response, return_type, max_retries, attempt + 1)
390
- else:
391
- logger.error(f"Resource exhausted on final parsing attempt: {e}")
392
- raise
363
+ if isinstance(exc, (EmptyExtractorMessageError, IndexError, json.JSONDecodeError, ValueError)):
364
+ return RetryDecision(action=RetryAction.RETRY_NOW)
365
+ return RetryDecision(action=RetryAction.GIVE_UP)
366
+
367
+ def on_exhausted(exc: Exception):
368
+ if isinstance(exc, ResourceExhausted):
369
+ logger.error(f"Resource exhausted on final parsing attempt: {exc}")
370
+ raise exc
371
+ logger.error(f"Max retries ({max_retries}) reached for parsing response: {response}")
372
+ raise Exception(f"Max retries reached for parsing response: {response}")
393
373
 
394
- def _try_parse(self, message_content, parser):
395
- try:
396
- prompt_template = """You are an JSON expert. Here you need to extract information in the following json format: {format_instructions}
374
+ return with_retries(
375
+ call_once,
376
+ max_attempts=max(1, max_retries - attempt),
377
+ classify=classify,
378
+ on_exhausted=on_exhausted,
379
+ log_prefix="Parse response",
380
+ )
397
381
 
398
- Here is the content to parse and fix: {adjective}
382
+ def _structured_parse(self, message_content, parser, format_instructions: str | None = None):
383
+ if format_instructions is None:
384
+ format_instructions = parser.get_format_instructions()
385
+ prompt_template = """You are a JSON expert. Here you need to extract information in the following json format: {format_instructions}
399
386
 
400
- Please provide only the JSON output without any additional text."""
401
- prompt = PromptTemplate(
402
- template=prompt_template,
403
- input_variables=["adjective"],
404
- partial_variables={"format_instructions": parser.get_format_instructions()},
405
- )
406
- chain = prompt | self.parsing_llm | parser
387
+ Here is the content to parse and fix: {adjective}
388
+
389
+ Please provide only the JSON output without any additional text."""
390
+ prompt = PromptTemplate(
391
+ template=prompt_template,
392
+ input_variables=["adjective"],
393
+ partial_variables={"format_instructions": format_instructions},
394
+ )
395
+ chain = prompt | self.parsing_llm | parser
396
+ try:
407
397
  return chain.invoke(
408
398
  {"adjective": message_content},
409
399
  config={"callbacks": [MONITORING_CALLBACK, self.agent_monitoring_callback]},
@@ -411,7 +401,28 @@ class CodeBoardingAgent(ReferenceResolverMixin, MonitoringMixin):
411
401
  except (ValidationError, OutputParserException):
412
402
  for _, v in json.loads(message_content).items():
413
403
  try:
414
- return self._try_parse(json.dumps(v), parser)
404
+ return self._structured_parse(json.dumps(v), parser)
415
405
  except:
416
406
  pass
417
407
  raise ValueError(f"Couldn't parse {message_content}")
408
+
409
+ def _extractor_parse(self, response, return_type, parser, include_hidden: bool = False):
410
+ extractor = create_extractor(self.parsing_llm, tools=[return_type], tool_choice=return_type.__name__)
411
+ try:
412
+ result = extractor.invoke(
413
+ return_type.extractor_str(include_hidden=include_hidden) + response,
414
+ config={"callbacks": [MONITORING_CALLBACK, self.agent_monitoring_callback]},
415
+ )
416
+ except AttributeError as e:
417
+ if "tool_call_id" in str(e):
418
+ logger.warning(f"Trustcall bug encountered: {e}")
419
+ raise
420
+ raise
421
+ if "responses" in result and len(result["responses"]) != 0:
422
+ return return_type.model_validate(result["responses"][0])
423
+ if "messages" in result and len(result["messages"]) != 0:
424
+ message = result["messages"][0].content
425
+ if not message:
426
+ raise EmptyExtractorMessageError("Extractor returned empty message content")
427
+ return self._structured_parse(message, parser)
428
+ raise EmptyExtractorMessageError("Extractor returned no responses and no messages")