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.
- {codeboarding-0.10.4/codeboarding.egg-info → codeboarding-0.12.0}/PKG-INFO +22 -10
- {codeboarding-0.10.4 → codeboarding-0.12.0}/PYPI.md +10 -8
- {codeboarding-0.10.4 → codeboarding-0.12.0}/README.md +9 -9
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/agent.py +157 -146
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/agent_responses.py +192 -28
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/cluster_methods_mixin.py +53 -8
- codeboarding-0.12.0/agents/incremental_agent.py +787 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/llm_config.py +22 -9
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/__init__.py +4 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/abstract_prompt_factory.py +8 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/claude_prompts.py +91 -34
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/deepseek_prompts.py +77 -16
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/gemini_flash_prompts.py +64 -28
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/glm_prompts.py +80 -10
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/gpt_prompts.py +68 -28
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/kimi_prompts.py +71 -24
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/prompts/prompt_factory.py +8 -0
- codeboarding-0.12.0/agents/retry.py +118 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/toolkit.py +0 -8
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/validation.py +78 -30
- {codeboarding-0.10.4 → codeboarding-0.12.0/codeboarding.egg-info}/PKG-INFO +22 -10
- {codeboarding-0.10.4 → codeboarding-0.12.0}/codeboarding.egg-info/SOURCES.txt +24 -10
- {codeboarding-0.10.4 → codeboarding-0.12.0}/codeboarding.egg-info/requires.txt +11 -1
- {codeboarding-0.10.4 → codeboarding-0.12.0}/codeboarding.egg-info/top_level.txt +2 -1
- codeboarding-0.12.0/codeboarding_cli/__init__.py +1 -0
- codeboarding-0.12.0/codeboarding_cli/bootstrap.py +53 -0
- codeboarding-0.12.0/codeboarding_cli/commands/__init__.py +1 -0
- codeboarding-0.12.0/codeboarding_cli/commands/full_analysis.py +206 -0
- codeboarding-0.12.0/codeboarding_cli/commands/incremental_analysis.py +139 -0
- codeboarding-0.12.0/codeboarding_cli/commands/partial_analysis.py +68 -0
- codeboarding-0.12.0/codeboarding_workflows/__init__.py +15 -0
- codeboarding-0.12.0/codeboarding_workflows/analysis.py +238 -0
- codeboarding-0.12.0/codeboarding_workflows/orchestration.py +48 -0
- codeboarding-0.12.0/codeboarding_workflows/rendering.py +92 -0
- codeboarding-0.12.0/codeboarding_workflows/sources/__init__.py +12 -0
- codeboarding-0.12.0/codeboarding_workflows/sources/local.py +23 -0
- codeboarding-0.12.0/codeboarding_workflows/sources/remote.py +71 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/core/__init__.py +2 -1
- {codeboarding-0.10.4 → codeboarding-0.12.0}/core/protocols.py +2 -1
- codeboarding-0.12.0/diagram_analysis/__init__.py +22 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/diagram_analysis/analysis_json.py +6 -1
- codeboarding-0.12.0/diagram_analysis/cluster_delta.py +455 -0
- codeboarding-0.12.0/diagram_analysis/cluster_snapshot.py +101 -0
- codeboarding-0.12.0/diagram_analysis/diagram_generator.py +735 -0
- codeboarding-0.12.0/diagram_analysis/exceptions.py +43 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/diagram_analysis/io_utils.py +145 -34
- codeboarding-0.12.0/diagram_analysis/run_mode.py +10 -0
- codeboarding-0.12.0/github_action.py +130 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/cohesion.py +6 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/runner.py +9 -3
- {codeboarding-0.10.4 → codeboarding-0.12.0}/install.py +1 -1
- {codeboarding-0.10.4 → codeboarding-0.12.0}/logging_config.py +1 -1
- codeboarding-0.12.0/main.py +93 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/paths.py +4 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/pyproject.toml +44 -4
- {codeboarding-0.10.4 → codeboarding-0.12.0}/repo_utils/__init__.py +2 -5
- codeboarding-0.12.0/repo_utils/change_detector.py +315 -0
- codeboarding-0.12.0/repo_utils/diff_parser.py +290 -0
- codeboarding-0.12.0/repo_utils/git_ops.py +319 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/repo_utils/ignore.py +33 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/__init__.py +308 -266
- codeboarding-0.12.0/static_analyzer/analysis_cache.py +475 -0
- codeboarding-0.12.0/static_analyzer/analysis_result.py +273 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/cluster_helpers.py +52 -30
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/constants.py +29 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/csharp_adapter.py +3 -3
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/go_adapter.py +3 -2
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/java_adapter.py +3 -3
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/php_adapter.py +3 -3
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/python_adapter.py +3 -2
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/rust_adapter.py +5 -4
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/typescript_adapter.py +5 -4
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/language_adapter.py +15 -2
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/graph.py +110 -22
- codeboarding-0.12.0/static_analyzer/incremental_orchestrator.py +125 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/java_utils.py +8 -10
- codeboarding-0.12.0/static_analyzer/language_results.py +128 -0
- codeboarding-0.12.0/static_analyzer/leiden_utils.py +103 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/reference_resolve_mixin.py +8 -4
- codeboarding-0.12.0/static_analyzer/typescript_config_scanner.py +235 -0
- codeboarding-0.12.0/tests/test_cli_parser.py +70 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_github_action.py +23 -11
- codeboarding-0.12.0/tests/test_main.py +578 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/user_config.py +2 -2
- {codeboarding-0.10.4 → codeboarding-0.12.0}/utils.py +31 -1
- codeboarding-0.10.4/agents/tools/read_git_diff.py +0 -131
- codeboarding-0.10.4/diagram_analysis/__init__.py +0 -3
- codeboarding-0.10.4/diagram_analysis/diagram_generator.py +0 -396
- codeboarding-0.10.4/diagram_analysis/incremental_types.py +0 -70
- codeboarding-0.10.4/diagram_analysis/incremental_updater.py +0 -400
- codeboarding-0.10.4/duckdb_crud.py +0 -125
- codeboarding-0.10.4/github_action.py +0 -173
- codeboarding-0.10.4/health/constants.py +0 -19
- codeboarding-0.10.4/health_main.py +0 -151
- codeboarding-0.10.4/main.py +0 -567
- codeboarding-0.10.4/repo_utils/change_detector.py +0 -294
- codeboarding-0.10.4/repo_utils/git_diff.py +0 -74
- codeboarding-0.10.4/repo_utils/method_diff.py +0 -162
- codeboarding-0.10.4/static_analyzer/analysis_cache.py +0 -761
- codeboarding-0.10.4/static_analyzer/analysis_result.py +0 -450
- codeboarding-0.10.4/static_analyzer/cluster_change_analyzer.py +0 -391
- codeboarding-0.10.4/static_analyzer/git_diff_analyzer.py +0 -224
- codeboarding-0.10.4/static_analyzer/incremental_orchestrator.py +0 -644
- codeboarding-0.10.4/static_analyzer/typescript_config_scanner.py +0 -54
- codeboarding-0.10.4/tests/test_main.py +0 -511
- {codeboarding-0.10.4 → codeboarding-0.12.0}/LICENSE +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/__init__.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/abstraction_agent.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/change_status.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/cluster_budget.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/constants.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/dependency_discovery.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/details_agent.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/meta_agent.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/model_capabilities.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/planner_agent.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/__init__.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/base.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/get_external_deps.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/get_method_invocations.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_cfg.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_docs.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_file.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_file_structure.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_packages.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_source.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/agents/tools/read_structure.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/caching/__init__.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/caching/cache.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/caching/details_cache.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/caching/meta_cache.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/codeboarding.egg-info/dependency_links.txt +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/codeboarding.egg-info/entry_points.txt +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/constants.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/core/plugin_loader.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/core/registry.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/diagram_analysis/file_coverage.py +1 -1
- {codeboarding-0.10.4 → codeboarding-0.12.0}/diagram_analysis/run_context.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/diagram_analysis/version.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/__init__.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/__init__.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/circular_deps.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/coupling.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/function_size.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/god_class.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/inheritance.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/instability.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/checks/unused_code_diagnostics.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/config.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/health/models.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/__init__.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/callbacks.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/context.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/mixin.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/stats.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/monitoring/writers.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/output_generators/__init__.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/output_generators/html.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/output_generators/html_template.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/output_generators/markdown.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/output_generators/mdx.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/output_generators/sphinx.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/repo_utils/errors.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/setup.cfg +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/cfg_skip_planner.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/cluster_relations.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/csharp_config_scanner.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/__init__.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/adapters/__init__.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/call_graph_builder.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/edge_build_context.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/edge_builder.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/hierarchy_builder.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/lsp_client.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/lsp_constants.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/models.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/progress.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/protocols.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/result_converter.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/source_inspector.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/symbol_table.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/engine/utils.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/java_config_scanner.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/lsp_client/__init__.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/lsp_client/diagnostics.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/node.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/programming_language.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/static_analyzer/scanner.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_install.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_logging_config.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_pyproject_packages.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_registry_coverage.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_tool_registry.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_user_config.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_vscode_constants.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_windows_compatibility.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tests/test_windows_encoding.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tool_registry/__init__.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tool_registry/installers.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tool_registry/manifest.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tool_registry/paths.py +0 -0
- {codeboarding-0.10.4 → codeboarding-0.12.0}/tool_registry/registry.py +0 -0
- {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.
|
|
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
|
-
| `--
|
|
200
|
-
| `--
|
|
201
|
-
| `--
|
|
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
|
|
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
|
-
| `--
|
|
140
|
-
| `--
|
|
141
|
-
| `--
|
|
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
|
|
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
|
|
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 --
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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,
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
logger.warning(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
389
|
-
return
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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.
|
|
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")
|