Skip to content

Commit ba4ab2b

Browse files
committed
Applied several fixes.
1 parent 64e69d5 commit ba4ab2b

10 files changed

Lines changed: 154 additions & 116 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Fixed
11+
12+
- `reply_reviewer` node of `ReviewAddressorAgent` was not using the correct tool to reply to the reviewer comments. We completely refactored the agent to turn it more reliable and robust.
13+
- `SearchCodeSnippetsTool` was being called with `repository` parameter even when `repo_id` was being provided, leading to errors. Now we support conditionally add the `repository` parameter to the signature of the tool.
14+
- Sometimes `Document.id` was being defined as an uuid when retrieving the document from the database, leading to errors..
15+
1016
## [0.1.3] - 2025-05-20
1117

1218
### Added

daiv/automation/agents/codebase_chat/agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def compile(self) -> CompiledGraph:
3939
return create_react_agent(
4040
self.get_model(model=settings.MODEL_NAME, temperature=settings.TEMPERATURE),
4141
state_schema=CodebaseChatAgentState,
42-
tools=[SearchCodeSnippetsTool(api_wrapper=index)],
42+
tools=[SearchCodeSnippetsTool(api_wrapper=index, all_repositories=True)],
4343
prompt=ChatPromptTemplate.from_messages([codebase_chat_system, MessagesPlaceholder("messages")]).partial(
4444
repositories=index._get_all_repositories(),
4545
search_code_snippets_name=SEARCH_CODE_SNIPPETS_NAME,
@@ -60,7 +60,7 @@ async def acompile(self) -> CompiledGraph:
6060
return create_react_agent(
6161
self.get_model(model=settings.MODEL_NAME, temperature=settings.TEMPERATURE),
6262
state_schema=CodebaseChatAgentState,
63-
tools=[SearchCodeSnippetsTool(api_wrapper=index)],
63+
tools=[SearchCodeSnippetsTool(api_wrapper=index, all_repositories=True)],
6464
prompt=ChatPromptTemplate.from_messages([codebase_chat_system, MessagesPlaceholder("messages")]).partial(
6565
repositories=await sync_to_async(index._get_all_repositories)(),
6666
search_code_snippets_name=SEARCH_CODE_SNIPPETS_NAME,

daiv/automation/agents/codebase_chat/prompts.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
“Sorry, I can only help with questions about the repositories i have access.”
2020
- Otherwise, continue.
2121
22-
2. **Analysis**For repository-related queries, extract:
22+
2. **Analysis** For repository-related queries, extract:
2323
- Programming languages / frameworks (with a brief in-code example).
2424
- Key search terms (ranked by relevance, with how each might appear in code).
2525
- Main concepts or topics (ranked, with a short why-it-matters note).
@@ -29,7 +29,7 @@
2929
3030
<repository_search>
3131
- Use **`{{ search_code_snippets_name }}`** only when the query pertains to these repositories.
32-
- Always follow the tools schema exactly.
32+
- Always follow the tool's schema exactly.
3333
- Search with the keywords you extracted; batch similar searches together.
3434
</repository_search>
3535
@@ -48,13 +48,14 @@
4848
```
4949
*Omit the “References” section if you did not cite code.*
5050
</crafting_the_reply>
51-
51+
{% if repositories %}
5252
<repositories_accessible_to_daiv>
5353
DAIV has access to the following repositories:
5454
{% for repository in repositories %}
5555
- {{ repository }}
5656
{%- endfor %}
5757
</repositories_accessible_to_daiv>
58+
{% endif %}
5859
""", # noqa: E501
5960
"jinja2",
6061
)

daiv/automation/agents/review_addressor/agent.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate
99
from langchain_core.runnables import Runnable, RunnableConfig
1010
from langgraph.graph import END, StateGraph
11-
from langgraph.graph.state import CompiledStateGraph
11+
from langgraph.graph.state import CompiledGraph, CompiledStateGraph
1212
from langgraph.prebuilt import create_react_agent
1313
from langgraph.store.base import BaseStore # noqa: TC002
1414
from langgraph.types import Command
1515

1616
from automation.agents import BaseAgent
1717
from automation.agents.plan_and_execute import PlanAndExecuteAgent
18+
from automation.tools import think
1819
from automation.tools.toolkits import ReadRepositoryToolkit, WebSearchToolkit
1920
from codebase.clients import RepoClient
2021
from codebase.indexes import CodebaseIndex
@@ -24,7 +25,6 @@
2425
from .prompts import respond_reviewer_system, review_comment_system, review_plan_system_template
2526
from .schemas import ReviewCommentEvaluation, ReviewCommentInput
2627
from .state import OverallState, ReplyAgentState
27-
from .tools import reply_reviewer_tool
2828

2929
logger = logging.getLogger("daiv.agents")
3030

@@ -41,6 +41,28 @@ def compile(self) -> Runnable:
4141
).with_config({"run_name": "ReviewCommentEvaluator"})
4242

4343

44+
class ReplyReviewerAgent(BaseAgent[CompiledGraph]):
45+
"""
46+
Agent to reply to reviewer's comments or questions.
47+
"""
48+
49+
def compile(self) -> CompiledGraph:
50+
tools = ReadRepositoryToolkit.create_instance().get_tools() + WebSearchToolkit.create_instance().get_tools()
51+
52+
return create_react_agent(
53+
self.get_model(model=settings.REPLY_MODEL_NAME, temperature=settings.REPLY_TEMPERATURE),
54+
state_schema=ReplyAgentState,
55+
tools=tools + [think],
56+
store=self.store,
57+
checkpointer=False,
58+
prompt=ChatPromptTemplate.from_messages([respond_reviewer_system, MessagesPlaceholder("messages")]).partial(
59+
current_date_time=timezone.now().strftime("%d %B, %Y %H:%M")
60+
),
61+
name=settings.REPLY_NAME,
62+
version="v2",
63+
)
64+
65+
4466
class ReviewAddressorAgent(BaseAgent[CompiledStateGraph]):
4567
"""
4668
Agent to address reviews by providing feedback and asking questions.
@@ -138,26 +160,8 @@ def reply_reviewer(
138160
Returns:
139161
Command[Literal["__end__"]]: The next step in the workflow.
140162
"""
141-
tools = ReadRepositoryToolkit.create_instance().get_tools() + WebSearchToolkit.create_instance().get_tools()
142-
143-
react_agent = create_react_agent(
144-
self.get_model(model=settings.REPLY_MODEL_NAME, temperature=settings.REPLY_TEMPERATURE),
145-
state_schema=ReplyAgentState,
146-
tools=tools + [reply_reviewer_tool],
147-
store=store,
148-
checkpointer=False,
149-
# FIXME: Add diff hunk referenced file to the prompt to improve the agent's performance
150-
prompt=ChatPromptTemplate.from_messages([respond_reviewer_system, MessagesPlaceholder("messages")]),
151-
name="reply_reviewer_react_agent",
152-
version="v2",
153-
)
163+
reply_reviewer_agent = ReplyReviewerAgent(store=store).agent
154164

155-
result = react_agent.invoke({
156-
"messages": state["notes"],
157-
"diff": state["diff"],
158-
"current_date_time": timezone.now().strftime("%d %B, %Y %H:%M"),
159-
})
165+
result = reply_reviewer_agent.invoke({"messages": state["notes"], "diff": state["diff"]})
160166

161-
# The reply is updated in the state by the reply_reviewer tool.
162-
# There's cases where the tool is not called, so we use the last message content as the reply.
163167
return Command(goto=END, update={"reply": result["messages"][-1].content})

daiv/automation/agents/review_addressor/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class ReviewAddressorSettings(BaseSettings):
1111
REVIEW_COMMENT_MODEL_NAME: ModelName | str = Field(
1212
default=ModelName.GPT_4_1_MINI, description="Model name to be used for review assessment."
1313
)
14+
REPLY_NAME: str = Field(default="ReplyReviewer", description="Name of the reply reviewer agent.")
1415
REPLY_MODEL_NAME: ModelName | str = Field(
1516
default=ModelName.GPT_4_1, description="Model name to be used for reply to comments or questions."
1617
)

daiv/automation/agents/review_addressor/prompts.py

Lines changed: 63 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -45,59 +45,69 @@
4545
)
4646

4747
respond_reviewer_system = SystemMessagePromptTemplate.from_template(
48-
"""You are a senior software developer.
49-
Your role is to give **insightful, professional, constructive replies** to comments or questions left on a merge-request review.
50-
51-
_Current date & time: {{ current_date_time }}_
52-
53-
## 1 Context you receive
54-
* **Reviewer's comment / question**
55-
* **Diff hunk** - the file name(s) and exact line(s) of code to which the reviewer is referring:
56-
57-
<diff_hunk>
58-
{{ diff }}
59-
</diff_hunk>
60-
61-
*You may also call tooling that inspects the wider codebase.*
62-
63-
> **Reference rules for ambiguous words**
64-
> If the reviewer says “this”, “here”, “below”, etc., assume the word refers to the line(s) shown in the diff hunk or the immediately neighbouring content of that file.
65-
66-
## 2 If the comment is vague
67-
If the reviewer's message is too ambiguous for a grounded reply, **do not analyse**.
68-
Instead, call the `answer_reviewer` tool **once** in this turn to ask a clarifying question, then stop.
69-
Resume the normal flow only after clarification is provided in a later turn.
70-
71-
## 3 Analysis block *(only when the comment is clear)*
72-
Wrap your deep-dive analysis inside **exactly one** pair of tags:
73-
74-
```xml
75-
<analysis>
76-
- Restate the reviewer's comment in your own words.
77-
- Quote the relevant lines from the diff hunk (include the leading +/- markers if present).
78-
- Explain how the comment relates to those lines.
79-
- Consider wider code-base context (using tools if helpful).
80-
- Discuss functionality, performance, maintainability, edge-cases, and possible bugs.
81-
- Suggest improvements or alternatives (do **not** change code directly).
82-
- Summarise overall impact and prioritise the findings.
83-
</analysis>
84-
```
85-
86-
## 4 Final reply to the reviewer
87-
Call `reply_reviewer` tool immediately **after** your analysis (or directly, when Step 2 triggered clarification).
88-
89-
* Use first-person (“I suggest…”, “I noticed…”).
90-
* Use the same language as the reviewer.
91-
* Provide technical explanations, but **do not add meta text** such as “Here is my answer” or “Hope this helps.”
92-
* Never mention the term “diff hunk” in the reply.
93-
94-
## 5 Tool-usage conventions
95-
* You may call other code-inspection tools if needed.
96-
* If you make multiple *independent* tool calls, place them together in one `function_calls` block.
97-
98-
---
99-
100-
Follow the steps above to reply to the reviewer's next comment or question.
48+
"""You are a senior software engineer tasked with writing **accurate, professional replies** to merge-request review comments.
49+
50+
51+
────────────────────────────────────────────────────────
52+
CURRENT DATE-TIME: {{ current_date_time }}
53+
54+
INCOMING CONTEXT
55+
• Reviewer's comment / question
56+
• Code excerpt (file name + exact lines):
57+
58+
<code_diff>
59+
{{ diff }}
60+
</code_diff>
61+
62+
AVAILABLE TOOLS
63+
• web_search
64+
• repository_structure
65+
• retrieve_file_content
66+
• search_code_snippets
67+
• think ← private chain-of-thought
68+
69+
────────────────────────────────────────────────────────
70+
WORKFLOW
71+
72+
### Step 0 • Decide if clarification is needed
73+
If the reviewer's message is too vague for a grounded answer:
74+
75+
1. Output **one** clarifying question addressed to the reviewer.
76+
2. Do **not** call any tools.
77+
3. End the turn.
78+
79+
### Step 1 • Decide whether extra context is required
80+
Ask yourself: *“Can I answer confidently from the diff alone?”*
81+
• **If yes** → skip directly to Step 2.
82+
• **If no** → call whichever inspection tools supply the missing context.
83+
- Group multiple calls in a single JSON array.
84+
- Stop once you have enough information.
85+
86+
### Step 2 • Private reasoning
87+
Call the `think` tool **exactly once**, with a `thought` field that includes:
88+
• Why you did or did not need extra tools.
89+
• Insights gleaned from any tool responses.
90+
• How these insights address the reviewer's comment.
91+
• Discussion of functionality, performance, maintainability, edge-cases, bugs.
92+
• Suggested improvements (do **not** edit code directly).
93+
• Impact / priority summary.
94+
(≈ 250 words max; this content is never shown to the reviewer.)
95+
96+
### Step 3 • Final reply shown to the reviewer
97+
Immediately after the `think` call, emit plain text following:
98+
• First-person voice (“I suggest…”, “I noticed…”).
99+
• Match the reviewer's language if detection is confident; otherwise use English.
100+
• Be technically precise, referencing code generically (“the line above/below”); **never** say “diff hunk”.
101+
• Concise yet complete; avoid unnecessary verbosity.
102+
103+
────────────────────────────────────────────────────────
104+
RULES OF THUMB
105+
• Ground every claim in evidence from the diff or tools; avoid speculation.
106+
• If you skipped the inspection tools, your `think` notes must state why the diff alone sufficed.
107+
• Keep total output lean; no superfluous headings or meta comments.
108+
109+
────────────────────────────────────────────────────────
110+
Follow this workflow for the reviewer's next comment.
101111
""", # noqa: E501
102112
"jinja2",
103113
)

daiv/automation/tools/repository.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import logging
44
import textwrap
5+
from typing import Any
56

67
from langchain_core.runnables import RunnableConfig # noqa: TC002
78
from langchain_core.tools import BaseTool
@@ -19,6 +20,7 @@
1920

2021
from .schemas import (
2122
CreateNewRepositoryFileInput,
23+
CrossSearchCodeSnippetsInput,
2224
DeleteRepositoryFileInput,
2325
RenameRepositoryFileInput,
2426
ReplaceSnippetInFileInput,
@@ -48,11 +50,17 @@ class SearchCodeSnippetsTool(BaseTool):
4850
""" # noqa: E501
4951
).format(retrieve_file_content_name=RETRIEVE_FILE_CONTENT_NAME)
5052

51-
args_schema: type[BaseModel] = SearchCodeSnippetsInput
5253
handle_validation_error: bool = True
5354

5455
api_wrapper: CodebaseIndex = Field(default_factory=lambda: CodebaseIndex(repo_client=RepoClient.create_instance()))
5556

57+
def __init__(self, *, all_repositories: bool = False, **kwargs: Any) -> None:
58+
super().__init__(**kwargs)
59+
if all_repositories:
60+
self.args_schema = CrossSearchCodeSnippetsInput
61+
else:
62+
self.args_schema = SearchCodeSnippetsInput
63+
5664
def _run(self, query: str, intent: str, config: RunnableConfig, repository: str | None = None) -> str:
5765
"""
5866
Searches the codebase for a given query.

daiv/automation/tools/schemas.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,21 @@ class SearchCodeSnippetsInput(BaseModel):
2323
""" # noqa: E501
2424
),
2525
)
26+
intent: str = Field(..., description="A brief description of why you are searching for this code.")
27+
28+
29+
class CrossSearchCodeSnippetsInput(SearchCodeSnippetsInput):
30+
"""
31+
Search for code snippets in specific repositories or all repositories.
32+
"""
33+
2634
repository: str | None = Field(
2735
default=None,
2836
description=(
2937
"The name of the repository to search in. "
3038
"If not provided, the search will be performed in all repositories."
3139
),
3240
)
33-
intent: str = Field(..., description="A brief description of why you are searching for this code.")
3441

3542

3643
class RepositoryStructureInput(BaseModel):

daiv/codebase/search_engines/retrievers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def _get_relevant_documents(self, query: str, *, run_manager: CallbackManagerFor
155155
)
156156
return [
157157
Document(
158-
id=document.uuid,
158+
id=str(document.uuid),
159159
page_content=document.page_content,
160160
metadata={
161161
"id": str(document.uuid),

0 commit comments

Comments
 (0)