code-graph-builder 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_graph_builder/__init__.py +82 -0
- code_graph_builder/builder.py +366 -0
- code_graph_builder/cgb_cli.py +32 -0
- code_graph_builder/cli.py +564 -0
- code_graph_builder/commands_cli.py +1288 -0
- code_graph_builder/config.py +340 -0
- code_graph_builder/constants.py +708 -0
- code_graph_builder/embeddings/__init__.py +40 -0
- code_graph_builder/embeddings/qwen3_embedder.py +573 -0
- code_graph_builder/embeddings/vector_store.py +584 -0
- code_graph_builder/examples/__init__.py +0 -0
- code_graph_builder/examples/example_configuration.py +276 -0
- code_graph_builder/examples/example_kuzu_usage.py +109 -0
- code_graph_builder/examples/example_semantic_search_full.py +347 -0
- code_graph_builder/examples/generate_wiki.py +915 -0
- code_graph_builder/examples/graph_export_example.py +100 -0
- code_graph_builder/examples/rag_example.py +206 -0
- code_graph_builder/examples/test_cli_demo.py +129 -0
- code_graph_builder/examples/test_embedding_api.py +153 -0
- code_graph_builder/examples/test_kuzu_local.py +190 -0
- code_graph_builder/examples/test_rag_redis.py +390 -0
- code_graph_builder/graph_updater.py +605 -0
- code_graph_builder/guidance/__init__.py +1 -0
- code_graph_builder/guidance/agent.py +123 -0
- code_graph_builder/guidance/prompts.py +74 -0
- code_graph_builder/guidance/toolset.py +264 -0
- code_graph_builder/language_spec.py +536 -0
- code_graph_builder/mcp/__init__.py +21 -0
- code_graph_builder/mcp/api_doc_generator.py +764 -0
- code_graph_builder/mcp/file_editor.py +207 -0
- code_graph_builder/mcp/pipeline.py +777 -0
- code_graph_builder/mcp/server.py +161 -0
- code_graph_builder/mcp/tools.py +1800 -0
- code_graph_builder/models.py +115 -0
- code_graph_builder/parser_loader.py +344 -0
- code_graph_builder/parsers/__init__.py +7 -0
- code_graph_builder/parsers/call_processor.py +306 -0
- code_graph_builder/parsers/call_resolver.py +139 -0
- code_graph_builder/parsers/definition_processor.py +796 -0
- code_graph_builder/parsers/factory.py +119 -0
- code_graph_builder/parsers/import_processor.py +293 -0
- code_graph_builder/parsers/structure_processor.py +145 -0
- code_graph_builder/parsers/type_inference.py +143 -0
- code_graph_builder/parsers/utils.py +134 -0
- code_graph_builder/rag/__init__.py +68 -0
- code_graph_builder/rag/camel_agent.py +429 -0
- code_graph_builder/rag/client.py +298 -0
- code_graph_builder/rag/config.py +239 -0
- code_graph_builder/rag/cypher_generator.py +67 -0
- code_graph_builder/rag/llm_backend.py +210 -0
- code_graph_builder/rag/markdown_generator.py +352 -0
- code_graph_builder/rag/prompt_templates.py +440 -0
- code_graph_builder/rag/rag_engine.py +640 -0
- code_graph_builder/rag/review_report.md +172 -0
- code_graph_builder/rag/tests/__init__.py +3 -0
- code_graph_builder/rag/tests/test_camel_agent.py +313 -0
- code_graph_builder/rag/tests/test_client.py +221 -0
- code_graph_builder/rag/tests/test_config.py +177 -0
- code_graph_builder/rag/tests/test_markdown_generator.py +240 -0
- code_graph_builder/rag/tests/test_prompt_templates.py +160 -0
- code_graph_builder/services/__init__.py +39 -0
- code_graph_builder/services/graph_service.py +465 -0
- code_graph_builder/services/kuzu_service.py +665 -0
- code_graph_builder/services/memory_service.py +171 -0
- code_graph_builder/settings.py +75 -0
- code_graph_builder/tests/ACCEPTANCE_CRITERIA_PHASE2.md +401 -0
- code_graph_builder/tests/__init__.py +1 -0
- code_graph_builder/tests/run_acceptance_check.py +378 -0
- code_graph_builder/tests/test_api_find.py +231 -0
- code_graph_builder/tests/test_api_find_integration.py +226 -0
- code_graph_builder/tests/test_basic.py +78 -0
- code_graph_builder/tests/test_c_api_extraction.py +388 -0
- code_graph_builder/tests/test_call_resolution_scenarios.py +504 -0
- code_graph_builder/tests/test_embedder.py +411 -0
- code_graph_builder/tests/test_integration_semantic.py +434 -0
- code_graph_builder/tests/test_mcp_protocol.py +298 -0
- code_graph_builder/tests/test_mcp_user_flow.py +190 -0
- code_graph_builder/tests/test_rag.py +404 -0
- code_graph_builder/tests/test_settings.py +135 -0
- code_graph_builder/tests/test_step1_graph_build.py +264 -0
- code_graph_builder/tests/test_step2_api_docs.py +323 -0
- code_graph_builder/tests/test_step3_embedding.py +278 -0
- code_graph_builder/tests/test_vector_store.py +552 -0
- code_graph_builder/tools/__init__.py +40 -0
- code_graph_builder/tools/graph_query.py +495 -0
- code_graph_builder/tools/semantic_search.py +387 -0
- code_graph_builder/types.py +333 -0
- code_graph_builder/utils/__init__.py +0 -0
- code_graph_builder/utils/path_utils.py +30 -0
- code_graph_builder-0.2.0.dist-info/METADATA +321 -0
- code_graph_builder-0.2.0.dist-info/RECORD +93 -0
- code_graph_builder-0.2.0.dist-info/WHEEL +4 -0
- code_graph_builder-0.2.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Code Graph Builder - Parser Utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
from typing import TYPE_CHECKING, NamedTuple
|
|
8
|
+
|
|
9
|
+
from loguru import logger
|
|
10
|
+
from tree_sitter import Node, Query, QueryCursor
|
|
11
|
+
|
|
12
|
+
from .. import constants as cs
|
|
13
|
+
from ..types import ASTNode, LanguageQueries, NodeType, PropertyDict, SimpleNameLookup
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from ..language_spec import LanguageSpec
|
|
17
|
+
from ..services import IngestorProtocol
|
|
18
|
+
from ..types import FunctionRegistryTrieProtocol
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FunctionCapturesResult(NamedTuple):
|
|
22
|
+
"""Result of capturing functions from AST."""
|
|
23
|
+
|
|
24
|
+
lang_config: LanguageSpec
|
|
25
|
+
captures: dict[str, list[ASTNode]]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_function_captures(
|
|
29
|
+
root_node: ASTNode,
|
|
30
|
+
language: cs.SupportedLanguage,
|
|
31
|
+
queries: dict[cs.SupportedLanguage, LanguageQueries],
|
|
32
|
+
) -> FunctionCapturesResult | None:
|
|
33
|
+
"""Get function captures from AST using Tree-sitter query."""
|
|
34
|
+
lang_queries = queries[language]
|
|
35
|
+
lang_config = lang_queries[cs.QUERY_CONFIG]
|
|
36
|
+
|
|
37
|
+
if not (query := lang_queries[cs.QUERY_FUNCTIONS]):
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
cursor = QueryCursor(query)
|
|
41
|
+
captures = cursor.captures(root_node)
|
|
42
|
+
return FunctionCapturesResult(lang_config, captures)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@lru_cache(maxsize=10000)
|
|
46
|
+
def _cached_decode_bytes(text_bytes: bytes) -> str:
|
|
47
|
+
"""Cached byte decoding for performance."""
|
|
48
|
+
return text_bytes.decode(cs.ENCODING_UTF8)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def safe_decode_text(node: ASTNode | None) -> str | None:
|
|
52
|
+
"""Safely decode node text to string."""
|
|
53
|
+
if node is None or (text_bytes := node.text) is None:
|
|
54
|
+
return None
|
|
55
|
+
if isinstance(text_bytes, bytes):
|
|
56
|
+
return _cached_decode_bytes(text_bytes)
|
|
57
|
+
return str(text_bytes)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def safe_decode_with_fallback(node: ASTNode | None, fallback: str = "") -> str:
|
|
61
|
+
"""Safely decode node text with fallback."""
|
|
62
|
+
return result if (result := safe_decode_text(node)) is not None else fallback
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def contains_node(parent: ASTNode, target: ASTNode) -> bool:
|
|
66
|
+
"""Check if parent contains target node."""
|
|
67
|
+
return parent == target or any(
|
|
68
|
+
contains_node(child, target) for child in parent.children
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def ingest_method(
|
|
73
|
+
method_node: ASTNode,
|
|
74
|
+
container_qn: str,
|
|
75
|
+
container_type: cs.NodeLabel,
|
|
76
|
+
ingestor: IngestorProtocol,
|
|
77
|
+
function_registry: FunctionRegistryTrieProtocol,
|
|
78
|
+
simple_name_lookup: SimpleNameLookup,
|
|
79
|
+
get_docstring_func: Callable[[ASTNode], str | None],
|
|
80
|
+
language: cs.SupportedLanguage | None = None,
|
|
81
|
+
extract_decorators_func: Callable[[ASTNode], list[str]] | None = None,
|
|
82
|
+
method_qualified_name: str | None = None,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Ingest a method node into the graph."""
|
|
85
|
+
# Extract method name
|
|
86
|
+
if language == cs.SupportedLanguage.CPP:
|
|
87
|
+
from .cpp import utils as cpp_utils
|
|
88
|
+
|
|
89
|
+
method_name = cpp_utils.extract_function_name(method_node)
|
|
90
|
+
if not method_name:
|
|
91
|
+
return
|
|
92
|
+
elif not (method_name_node := method_node.child_by_field_name(cs.FIELD_NAME)):
|
|
93
|
+
return
|
|
94
|
+
elif (text := method_name_node.text) is None:
|
|
95
|
+
return
|
|
96
|
+
else:
|
|
97
|
+
method_name = text.decode(cs.ENCODING_UTF8)
|
|
98
|
+
|
|
99
|
+
method_qn = method_qualified_name or f"{container_qn}.{method_name}"
|
|
100
|
+
|
|
101
|
+
decorators = extract_decorators_func(method_node) if extract_decorators_func else []
|
|
102
|
+
|
|
103
|
+
method_props: PropertyDict = {
|
|
104
|
+
cs.KEY_QUALIFIED_NAME: method_qn,
|
|
105
|
+
cs.KEY_NAME: method_name,
|
|
106
|
+
cs.KEY_DECORATORS: decorators,
|
|
107
|
+
cs.KEY_START_LINE: method_node.start_point[0] + 1,
|
|
108
|
+
cs.KEY_END_LINE: method_node.end_point[0] + 1,
|
|
109
|
+
cs.KEY_DOCSTRING: get_docstring_func(method_node),
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
logger.info(f" Found Method: {method_name} (qn: {method_qn})")
|
|
113
|
+
ingestor.ensure_node_batch(cs.NodeLabel.METHOD, method_props)
|
|
114
|
+
function_registry[method_qn] = NodeType.METHOD
|
|
115
|
+
simple_name_lookup[method_name].add(method_qn)
|
|
116
|
+
|
|
117
|
+
ingestor.ensure_relationship_batch(
|
|
118
|
+
(container_type, cs.KEY_QUALIFIED_NAME, container_qn),
|
|
119
|
+
cs.RelationshipType.DEFINES_METHOD,
|
|
120
|
+
(cs.NodeLabel.METHOD, cs.KEY_QUALIFIED_NAME, method_qn),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def is_method_node(func_node: ASTNode, lang_config: LanguageSpec) -> bool:
|
|
125
|
+
"""Check if a function node is actually a method."""
|
|
126
|
+
current = func_node.parent
|
|
127
|
+
if not isinstance(current, Node):
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
while current and current.type not in lang_config.module_node_types:
|
|
131
|
+
if current.type in lang_config.class_node_types:
|
|
132
|
+
return True
|
|
133
|
+
current = current.parent
|
|
134
|
+
return False
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""RAG module for code graph-based retrieval and generation.
|
|
2
|
+
|
|
3
|
+
This module provides RAG (Retrieval-Augmented Generation) capabilities
|
|
4
|
+
for code analysis using CAMEL framework and OpenAI-compatible LLM APIs.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from code_graph_builder.rag import RAGConfig, create_rag_engine
|
|
8
|
+
>>> from code_graph_builder.rag.camel_agent import CamelAgent
|
|
9
|
+
>>>
|
|
10
|
+
>>> config = RAGConfig.from_env()
|
|
11
|
+
>>> engine = create_rag_engine(config)
|
|
12
|
+
>>> result = engine.query("Explain the authentication flow")
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from .config import (
|
|
18
|
+
MoonshotConfig,
|
|
19
|
+
OutputConfig,
|
|
20
|
+
RAGConfig,
|
|
21
|
+
RetrievalConfig,
|
|
22
|
+
)
|
|
23
|
+
from .client import (
|
|
24
|
+
ChatResponse,
|
|
25
|
+
LLMClient,
|
|
26
|
+
create_llm_client,
|
|
27
|
+
)
|
|
28
|
+
from .markdown_generator import (
|
|
29
|
+
AnalysisResult,
|
|
30
|
+
MarkdownGenerator,
|
|
31
|
+
SourceReference,
|
|
32
|
+
)
|
|
33
|
+
from .prompt_templates import (
|
|
34
|
+
CodeAnalysisPrompts,
|
|
35
|
+
CodeContext,
|
|
36
|
+
RAGPrompts,
|
|
37
|
+
create_code_context,
|
|
38
|
+
)
|
|
39
|
+
from .rag_engine import (
|
|
40
|
+
RAGEngine,
|
|
41
|
+
RAGResult,
|
|
42
|
+
create_rag_engine,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
# Config
|
|
47
|
+
"RAGConfig",
|
|
48
|
+
"MoonshotConfig",
|
|
49
|
+
"RetrievalConfig",
|
|
50
|
+
"OutputConfig",
|
|
51
|
+
# Engine
|
|
52
|
+
"RAGEngine",
|
|
53
|
+
"RAGResult",
|
|
54
|
+
"create_rag_engine",
|
|
55
|
+
# LLM Client
|
|
56
|
+
"LLMClient",
|
|
57
|
+
"ChatResponse",
|
|
58
|
+
"create_llm_client",
|
|
59
|
+
# Prompts
|
|
60
|
+
"CodeAnalysisPrompts",
|
|
61
|
+
"RAGPrompts",
|
|
62
|
+
"CodeContext",
|
|
63
|
+
"create_code_context",
|
|
64
|
+
# Markdown
|
|
65
|
+
"MarkdownGenerator",
|
|
66
|
+
"AnalysisResult",
|
|
67
|
+
"SourceReference",
|
|
68
|
+
]
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
"""CAMEL Agent wrapper for RAG integration.
|
|
2
|
+
|
|
3
|
+
This module provides integration with the CAMEL framework for multi-agent
|
|
4
|
+
code analysis workflows.
|
|
5
|
+
|
|
6
|
+
Note: This is a simplified implementation that provides CAMEL-like interfaces
|
|
7
|
+
without requiring the full CAMEL framework dependency. It can be extended
|
|
8
|
+
to use the actual CAMEL library if needed.
|
|
9
|
+
|
|
10
|
+
Examples:
|
|
11
|
+
>>> from code_graph_builder.rag.camel_agent import CamelAgent
|
|
12
|
+
>>> agent = CamelAgent(
|
|
13
|
+
... role="Code Analyst",
|
|
14
|
+
... goal="Analyze code and provide insights",
|
|
15
|
+
... backstory="Expert in software architecture"
|
|
16
|
+
... )
|
|
17
|
+
>>> result = agent.analyze("Explain this function", context="def foo(): pass")
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from typing import TYPE_CHECKING, Any, Protocol
|
|
24
|
+
|
|
25
|
+
from loguru import logger
|
|
26
|
+
|
|
27
|
+
from .client import LLMClient, create_llm_client
|
|
28
|
+
from .prompt_templates import CodeAnalysisPrompts, CodeContext
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from .rag_engine import RAGEngine, RAGResult
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AgentResponse(Protocol):
|
|
35
|
+
"""Protocol for agent responses."""
|
|
36
|
+
|
|
37
|
+
content: str
|
|
38
|
+
metadata: dict[str, Any]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class CamelAgentResponse:
|
|
43
|
+
"""Response from CAMEL agent.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
content: Generated response content
|
|
47
|
+
metadata: Additional metadata
|
|
48
|
+
role: Agent role that generated the response
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
content: str
|
|
52
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
53
|
+
role: str = "agent"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CamelAgent:
|
|
57
|
+
"""CAMEL-style agent for code analysis.
|
|
58
|
+
|
|
59
|
+
Provides a CAMEL-like interface for single-agent code analysis tasks.
|
|
60
|
+
This implementation uses an OpenAI-compatible LLM as the underlying model.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
role: Agent's role (e.g., "Code Analyst")
|
|
64
|
+
goal: Agent's goal/objective
|
|
65
|
+
backstory: Agent's background/context
|
|
66
|
+
llm_client: LLM API client
|
|
67
|
+
verbose: Enable verbose logging
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
>>> agent = CamelAgent(
|
|
71
|
+
... role="Senior Python Developer",
|
|
72
|
+
... goal="Review code for best practices",
|
|
73
|
+
... backstory="10+ years of Python experience"
|
|
74
|
+
... )
|
|
75
|
+
>>> response = agent.analyze("Review this function", code="def foo(): pass")
|
|
76
|
+
>>> print(response.content)
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
role: str,
|
|
82
|
+
goal: str,
|
|
83
|
+
backstory: str,
|
|
84
|
+
llm_client: LLMClient | None = None,
|
|
85
|
+
verbose: bool = False,
|
|
86
|
+
):
|
|
87
|
+
self.role = role
|
|
88
|
+
self.goal = goal
|
|
89
|
+
self.backstory = backstory
|
|
90
|
+
self.llm_client = llm_client or create_llm_client()
|
|
91
|
+
self.verbose = verbose
|
|
92
|
+
self.prompts = CodeAnalysisPrompts()
|
|
93
|
+
|
|
94
|
+
# Build system prompt from role definition
|
|
95
|
+
self.system_prompt = self._build_system_prompt()
|
|
96
|
+
|
|
97
|
+
logger.info(f"Initialized CamelAgent: {role}")
|
|
98
|
+
|
|
99
|
+
def _build_system_prompt(self) -> str:
|
|
100
|
+
"""Build system prompt from agent definition."""
|
|
101
|
+
return f"""You are a {self.role}.
|
|
102
|
+
|
|
103
|
+
Your Goal: {self.goal}
|
|
104
|
+
|
|
105
|
+
Your Backstory: {self.backstory}
|
|
106
|
+
|
|
107
|
+
Guidelines:
|
|
108
|
+
1. Always stay in character as a {self.role}
|
|
109
|
+
2. Focus on achieving your stated goal
|
|
110
|
+
3. Use your expertise and background to provide insightful analysis
|
|
111
|
+
4. Be thorough but concise in your responses
|
|
112
|
+
5. When analyzing code, consider best practices, patterns, and potential issues
|
|
113
|
+
|
|
114
|
+
Respond in a professional, helpful manner."""
|
|
115
|
+
|
|
116
|
+
def analyze(
|
|
117
|
+
self,
|
|
118
|
+
task: str,
|
|
119
|
+
code: str | None = None,
|
|
120
|
+
context: str | None = None,
|
|
121
|
+
) -> CamelAgentResponse:
|
|
122
|
+
"""Analyze code or answer a question.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
task: Task description or question
|
|
126
|
+
code: Code to analyze (optional)
|
|
127
|
+
context: Additional context (optional)
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
CamelAgentResponse with analysis
|
|
131
|
+
"""
|
|
132
|
+
# Build user message
|
|
133
|
+
user_content = task
|
|
134
|
+
if code:
|
|
135
|
+
user_content += f"\n\n```python\n{code}\n```"
|
|
136
|
+
if context:
|
|
137
|
+
user_content += f"\n\nContext: {context}"
|
|
138
|
+
|
|
139
|
+
messages = [
|
|
140
|
+
{"role": "system", "content": self.system_prompt},
|
|
141
|
+
{"role": "user", "content": user_content},
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
response = self.llm_client.chat_with_messages(messages)
|
|
146
|
+
return CamelAgentResponse(
|
|
147
|
+
content=response.content,
|
|
148
|
+
metadata={
|
|
149
|
+
"usage": response.usage,
|
|
150
|
+
"model": response.model,
|
|
151
|
+
},
|
|
152
|
+
role=self.role,
|
|
153
|
+
)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.error(f"Agent analysis failed: {e}")
|
|
156
|
+
return CamelAgentResponse(
|
|
157
|
+
content=f"Error during analysis: {e}",
|
|
158
|
+
metadata={"error": str(e)},
|
|
159
|
+
role=self.role,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def review_code(
|
|
163
|
+
self,
|
|
164
|
+
code: str,
|
|
165
|
+
review_type: str = "general",
|
|
166
|
+
) -> CamelAgentResponse:
|
|
167
|
+
"""Review code for specific aspects.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
code: Code to review
|
|
171
|
+
review_type: Type of review (general, security, performance, style)
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Code review response
|
|
175
|
+
"""
|
|
176
|
+
review_prompts = {
|
|
177
|
+
"general": "Please review this code for general quality, correctness, and best practices.",
|
|
178
|
+
"security": "Please review this code for security vulnerabilities and best practices.",
|
|
179
|
+
"performance": "Please review this code for performance issues and optimization opportunities.",
|
|
180
|
+
"style": "Please review this code for code style, readability, and maintainability.",
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
prompt = review_prompts.get(review_type, review_prompts["general"])
|
|
184
|
+
|
|
185
|
+
return self.analyze(
|
|
186
|
+
task=f"{prompt}\n\nProvide specific recommendations with examples.",
|
|
187
|
+
code=code,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def explain_code(
|
|
191
|
+
self,
|
|
192
|
+
code: str,
|
|
193
|
+
detail_level: str = "medium",
|
|
194
|
+
) -> CamelAgentResponse:
|
|
195
|
+
"""Explain code in detail.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
code: Code to explain
|
|
199
|
+
detail_level: Level of detail (brief, medium, detailed)
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Code explanation
|
|
203
|
+
"""
|
|
204
|
+
detail_instructions = {
|
|
205
|
+
"brief": "Provide a brief, high-level summary of what this code does.",
|
|
206
|
+
"medium": "Explain this code with a balance of high-level overview and key details.",
|
|
207
|
+
"detailed": "Provide a detailed explanation covering all logic, edge cases, and design decisions.",
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
instruction = detail_instructions.get(detail_level, detail_instructions["medium"])
|
|
211
|
+
|
|
212
|
+
return self.analyze(
|
|
213
|
+
task=f"{instruction}\n\nFormat your response in markdown.",
|
|
214
|
+
code=code,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def suggest_improvements(
|
|
218
|
+
self,
|
|
219
|
+
code: str,
|
|
220
|
+
focus_areas: list[str] | None = None,
|
|
221
|
+
) -> CamelAgentResponse:
|
|
222
|
+
"""Suggest improvements for code.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
code: Code to improve
|
|
226
|
+
focus_areas: Specific areas to focus on (e.g., ["readability", "performance"])
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Improvement suggestions
|
|
230
|
+
"""
|
|
231
|
+
task = "Suggest improvements for this code."
|
|
232
|
+
|
|
233
|
+
if focus_areas:
|
|
234
|
+
task += f"\n\nFocus on: {', '.join(focus_areas)}"
|
|
235
|
+
|
|
236
|
+
task += "\n\nFor each suggestion, provide:\n1. The issue\n2. Why it matters\n3. A concrete improved example"
|
|
237
|
+
|
|
238
|
+
return self.analyze(task=task, code=code)
|
|
239
|
+
|
|
240
|
+
def answer_question(
|
|
241
|
+
self,
|
|
242
|
+
question: str,
|
|
243
|
+
code_context: str | None = None,
|
|
244
|
+
) -> CamelAgentResponse:
|
|
245
|
+
"""Answer a question about code.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
question: The question to answer
|
|
249
|
+
code_context: Relevant code context
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Answer response
|
|
253
|
+
"""
|
|
254
|
+
return self.analyze(
|
|
255
|
+
task=question,
|
|
256
|
+
code=code_context,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class MultiAgentRAG:
|
|
261
|
+
"""Multi-agent RAG system using CAMEL-style agents.
|
|
262
|
+
|
|
263
|
+
Coordinates multiple specialized agents for comprehensive code analysis.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
rag_engine: RAG engine for retrieval
|
|
267
|
+
verbose: Enable verbose logging
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
>>> multi_agent = MultiAgentRAG(rag_engine)
|
|
271
|
+
>>> result = multi_agent.analyze(
|
|
272
|
+
... query="Explain the authentication system",
|
|
273
|
+
... analysis_types=["architecture", "security"]
|
|
274
|
+
... )
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
def __init__(
|
|
278
|
+
self,
|
|
279
|
+
rag_engine: RAGEngine,
|
|
280
|
+
verbose: bool = False,
|
|
281
|
+
):
|
|
282
|
+
self.rag_engine = rag_engine
|
|
283
|
+
self.verbose = verbose
|
|
284
|
+
|
|
285
|
+
# Initialize specialized agents
|
|
286
|
+
self._init_agents()
|
|
287
|
+
|
|
288
|
+
def _init_agents(self) -> None:
|
|
289
|
+
"""Initialize specialized agents."""
|
|
290
|
+
self.architect = CamelAgent(
|
|
291
|
+
role="Software Architect",
|
|
292
|
+
goal="Analyze code architecture and design patterns",
|
|
293
|
+
backstory="Senior architect with 15+ years of experience in system design",
|
|
294
|
+
llm_client=self.rag_engine.llm_client,
|
|
295
|
+
verbose=self.verbose,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
self.security_expert = CamelAgent(
|
|
299
|
+
role="Security Engineer",
|
|
300
|
+
goal="Identify security vulnerabilities and best practices",
|
|
301
|
+
backstory="Security specialist with expertise in secure coding practices",
|
|
302
|
+
llm_client=self.rag_engine.llm_client,
|
|
303
|
+
verbose=self.verbose,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
self.performance_expert = CamelAgent(
|
|
307
|
+
role="Performance Engineer",
|
|
308
|
+
goal="Optimize code performance and resource usage",
|
|
309
|
+
backstory="Performance optimization specialist with deep knowledge of algorithms",
|
|
310
|
+
llm_client=self.rag_engine.llm_client,
|
|
311
|
+
verbose=self.verbose,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
self.documentation_writer = CamelAgent(
|
|
315
|
+
role="Technical Writer",
|
|
316
|
+
goal="Create clear, comprehensive documentation",
|
|
317
|
+
backstory="Technical writer specializing in developer documentation",
|
|
318
|
+
llm_client=self.rag_engine.llm_client,
|
|
319
|
+
verbose=self.verbose,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
def analyze(
|
|
323
|
+
self,
|
|
324
|
+
query: str,
|
|
325
|
+
analysis_types: list[str] | None = None,
|
|
326
|
+
) -> dict[str, CamelAgentResponse]:
|
|
327
|
+
"""Run multi-agent analysis on a query.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
query: User query
|
|
331
|
+
analysis_types: Types of analysis to run (architecture, security, performance, docs)
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Dictionary of agent responses
|
|
335
|
+
"""
|
|
336
|
+
if analysis_types is None:
|
|
337
|
+
analysis_types = ["architecture", "docs"]
|
|
338
|
+
|
|
339
|
+
# First, retrieve relevant code
|
|
340
|
+
rag_result = self.rag_engine.query(query)
|
|
341
|
+
|
|
342
|
+
# Build context from retrieved code
|
|
343
|
+
context_parts = []
|
|
344
|
+
for ctx in rag_result.contexts[:3]: # Limit to top 3 contexts
|
|
345
|
+
context_parts.append(ctx.format_context())
|
|
346
|
+
code_context = "\n\n---\n\n".join(context_parts)
|
|
347
|
+
|
|
348
|
+
# Run agent analyses
|
|
349
|
+
results: dict[str, CamelAgentResponse] = {}
|
|
350
|
+
|
|
351
|
+
if "architecture" in analysis_types:
|
|
352
|
+
results["architecture"] = self.architect.analyze(
|
|
353
|
+
task=f"Analyze the architecture and design patterns for: {query}",
|
|
354
|
+
context=code_context,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
if "security" in analysis_types:
|
|
358
|
+
results["security"] = self.security_expert.analyze(
|
|
359
|
+
task=f"Review security aspects of: {query}",
|
|
360
|
+
context=code_context,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
if "performance" in analysis_types:
|
|
364
|
+
results["performance"] = self.performance_expert.analyze(
|
|
365
|
+
task=f"Analyze performance characteristics of: {query}",
|
|
366
|
+
context=code_context,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
if "docs" in analysis_types:
|
|
370
|
+
results["documentation"] = self.documentation_writer.analyze(
|
|
371
|
+
task=f"Create documentation for: {query}",
|
|
372
|
+
context=code_context,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
return results
|
|
376
|
+
|
|
377
|
+
def comprehensive_review(
|
|
378
|
+
self,
|
|
379
|
+
qualified_name: str,
|
|
380
|
+
) -> dict[str, CamelAgentResponse]:
|
|
381
|
+
"""Run comprehensive review of a code entity.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
qualified_name: Fully qualified name of the entity
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
Dictionary of agent reviews
|
|
388
|
+
"""
|
|
389
|
+
# Get code explanation first
|
|
390
|
+
rag_result = self.rag_engine.explain_code(qualified_name)
|
|
391
|
+
|
|
392
|
+
# Get source code
|
|
393
|
+
code = ""
|
|
394
|
+
if rag_result.contexts:
|
|
395
|
+
code = rag_result.contexts[0].source_code
|
|
396
|
+
|
|
397
|
+
# Run all agents
|
|
398
|
+
results: dict[str, CamelAgentResponse] = {
|
|
399
|
+
"explanation": rag_result,
|
|
400
|
+
"architecture": self.architect.analyze(
|
|
401
|
+
task="Analyze the architecture and design patterns in this code",
|
|
402
|
+
code=code,
|
|
403
|
+
),
|
|
404
|
+
"security": self.security_expert.review_code(code, review_type="security"),
|
|
405
|
+
"performance": self.performance_expert.review_code(code, review_type="performance"),
|
|
406
|
+
"documentation": self.documentation_writer.explain_code(code, detail_level="detailed"),
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return results
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def create_camel_agent(
|
|
413
|
+
role: str,
|
|
414
|
+
goal: str,
|
|
415
|
+
backstory: str,
|
|
416
|
+
**kwargs: Any,
|
|
417
|
+
) -> CamelAgent:
|
|
418
|
+
"""Factory function to create a CAMEL agent.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
role: Agent role
|
|
422
|
+
goal: Agent goal
|
|
423
|
+
backstory: Agent backstory
|
|
424
|
+
**kwargs: Additional arguments for CamelAgent
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Configured CamelAgent
|
|
428
|
+
"""
|
|
429
|
+
return CamelAgent(role=role, goal=goal, backstory=backstory, **kwargs)
|