Skip to content

Commit 5995e27

Browse files
committed
✨ [WIP] Agents now take parent task history into account
- The AnalyzerAgent no longer uses hardcoded language, source, and target, but instead gathers it from the task context Signed-off-by: Fabian von Feilitzsch <[email protected]>
1 parent d225b5b commit 5995e27

File tree

17 files changed

+154
-47
lines changed

17 files changed

+154
-47
lines changed

kai/reactive_codeplanner/agent/analyzer_fix/agent.py

+38-26
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,27 @@
2323
@dataclass
2424
class _llm_response:
2525
reasoning: str | None
26-
java_file: str | None
26+
source_file: str | None
2727
addional_information: str | None
2828

2929

3030
class AnalyzerAgent(Agent):
31-
system_message = SystemMessage(
32-
content="""
33-
You are an experienced java developer, who specializes in migrating code to the Quarkus Framework
31+
system_message_template = Template(
32+
"""
33+
You are an experienced {{ language }} developer, who specializes in migrating code from {{ source }} to {{ target }}
34+
{{ background }}
3435
"""
3536
)
3637

3738
chat_message_template = Template(
3839
"""
39-
I will give you a JavaEE file for which I want to take one step towards migrating to Quarkus.
40+
I will give you a {{ source }} file for which I want to take one step towards migrating to {{ target }}.
4041
4142
I will provide you with static source code analysis information highlighting an issue which needs to be addressed.
4243
43-
I will also provide you with an example of how a similar issue was solved in the past via a solved example.
44-
45-
You can refer to the solved example for a pattern of how to update the input Java EE file to Quarkus.
46-
4744
Fix only the problem described. Other problems will be solved in subsequent steps so it is unnecessary to handle them now.
4845
49-
Before attempting to migrate the code to Quarkus reason through what changes are required and why.
46+
Before attempting to migrate the code to {{ target }} reason through what changes are required and why.
5047
5148
Pay attention to changes you make and impacts to external dependencies in the pom.xml as well as changes to imports we need to consider.
5249
@@ -83,8 +80,8 @@ class AnalyzerAgent(Agent):
8380
## Reasoning
8481
Write the step by step reasoning in this markdown section. If you are unsure of a step or reasoning, clearly state you are unsure and why.
8582
86-
## Updated Java File
87-
```java
83+
## Updated {{ language }} File
84+
```{{ language }}
8885
// Write the updated file for Quarkus in this section. If the file should be removed, make the content of the updated file a comment explaining it should be removed.
8986
```
9087
@@ -118,64 +115,79 @@ def execute(self, ask: AgentRequest) -> AnalyzerFixResponse:
118115

119116
language = guess_language(ask.file_content, file_name)
120117

118+
source = " and ".join(ask.sources)
119+
target = " and ".join(ask.targets)
120+
121+
system_message = SystemMessage(
122+
content=self.system_message_template.render(
123+
language=language,
124+
source=source,
125+
target=target,
126+
background=ask.background,
127+
)
128+
)
129+
121130
content = self.chat_message_template.render(
131+
source=source,
132+
target=target,
133+
language=language,
122134
src_file_contents=ask.file_content,
123135
src_file_name=file_name,
124136
src_file_language=language,
125137
incidents=ask.incidents,
126138
)
127139

128140
aimessage = self._model_provider.invoke(
129-
[self.system_message, HumanMessage(content=content)]
141+
[system_message, HumanMessage(content=content)]
130142
)
131143

132-
resp = self.parse_llm_response(aimessage)
144+
resp = self.parse_llm_response(aimessage, language)
133145
return AnalyzerFixResponse(
134146
encountered_errors=[],
135147
file_to_modify=Path(os.path.abspath(ask.file_path)),
136148
reasoning=resp.reasoning,
137149
additional_information=resp.addional_information,
138-
updated_file_content=resp.java_file,
150+
updated_file_content=resp.source_file,
139151
)
140152

141-
def parse_llm_response(self, message: BaseMessage) -> _llm_response:
153+
def parse_llm_response(self, message: BaseMessage, language: str) -> _llm_response:
142154
"""Private method that will be used to parse the contents and get the results"""
143155

144156
lines_of_output = cast(str, message.content).splitlines()
145157

146-
in_java_file = False
158+
in_source_file = False
147159
in_reasoning = False
148160
in_additional_details = False
149-
java_file = ""
161+
source_file = ""
150162
reasoning = ""
151163
additional_details = ""
152164
for line in lines_of_output:
153165
if line.strip() == "## Reasoning":
154166
in_reasoning = True
155-
in_java_file = False
167+
in_source_file = False
156168
in_additional_details = False
157169
continue
158-
if line.strip() == "## Updated Java File":
159-
in_java_file = True
170+
if line.strip() == f"## Updated {language} File":
171+
in_source_file = True
160172
in_reasoning = False
161173
in_additional_details = False
162174
continue
163175
if "## Additional Information" in line.strip():
164176
in_reasoning = False
165-
in_java_file = False
177+
in_source_file = False
166178
in_additional_details = True
167179
continue
168-
if in_java_file:
169-
if "```java" in line or "```" in line:
180+
if in_source_file:
181+
if f"```{language}" in line or "```" in line:
170182
continue
171-
java_file = "\n".join([java_file, line])
183+
source_file = "\n".join([source_file, line])
172184
if in_reasoning:
173185
reasoning = "\n".join([reasoning, line])
174186
if in_additional_details:
175187
additional_details = "\n".join([additional_details, line])
176188
return _llm_response(
177189
reasoning=reasoning,
178-
java_file=java_file,
190+
source_file=source_file,
179191
addional_information=additional_details,
180192
)
181193

kai/reactive_codeplanner/agent/analyzer_fix/api.py

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
class AnalyzerFixRequest(AgentRequest):
99
file_content: str
1010
incidents: list[Incident]
11+
sources: list[str]
12+
targets: list[str]
1113

1214

1315
@dataclass

kai/reactive_codeplanner/agent/api.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from abc import ABC, abstractmethod
22
from dataclasses import dataclass
33
from pathlib import Path
4+
from typing import Optional
45

56

67
@dataclass
78
class AgentRequest:
89
file_path: Path
10+
background: Optional[str]
911

1012

1113
@dataclass

kai/reactive_codeplanner/agent/dependency_agent/dependency_agent.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from pathlib import Path
33
from typing import Any, Callable, Optional, TypedDict, Union
44

5+
from jinja2 import Template
56
from langchain.prompts.chat import HumanMessagePromptTemplate
67
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
78

@@ -53,10 +54,10 @@ class _llm_response:
5354

5455
class MavenDependencyAgent(Agent):
5556

56-
sys_msg = SystemMessage(
57+
system_message_template = Template(
5758
"""
5859
You are an excellent java developer focused on updating dependencies in a maven `pom.xml` file.
59-
60+
{{ background }}
6061
### Guidelines:
6162
1 Only use the provided and predefined functions as the functions. Do not use any other functions.
6263
2 always search for the fqdn for the dependency to be added or updated
@@ -188,7 +189,13 @@ def execute(self, ask: AgentRequest) -> AgentResult:
188189
if not request.message:
189190
return AgentResult()
190191

191-
msg = [self.sys_msg, self.inst_msg_template.format(message=request.message)]
192+
system_message = SystemMessage(
193+
content=self.system_message_template.render(background=ask.background)
194+
)
195+
196+
content = self.inst_msg_template.format(message=request.message)
197+
198+
msg = [system_message, content]
192199
fix_gen_attempts = 0
193200
llm_response: Optional[_llm_response] = None
194201
maven_search: Optional[FQDNResponse] = None
@@ -252,11 +259,12 @@ def execute(self, ask: AgentRequest) -> AgentResult:
252259
logger.info("Need to call sub-agent for selecting FQDN")
253260
r = self.child_agent.execute(
254261
FQDNDependencySelectorRequest(
255-
request.file_path,
256-
request.message,
257-
a.code,
262+
file_path=request.file_path,
263+
msg=request.message,
264+
code=a.code,
258265
query=[],
259266
times=0,
267+
background=ask.background,
260268
)
261269
)
262270
if r.response is not None and isinstance(r.response, list):

kai/reactive_codeplanner/agent/dependency_agent/dependency_fqdn_selection.py

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def execute(self, ask: AgentRequest) -> FQDNDependencySelectorResult:
101101
code="",
102102
query=query,
103103
times=ask.times + 1,
104+
background=ask.background,
104105
)
105106
)
106107
if isinstance(response, list):

kai/reactive_codeplanner/agent/maven_compiler_fix/agent.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
33

44
from kai.llm_interfacing.model_provider import ModelProvider
5+
from kai.logging.logging import get_logger
56
from kai.reactive_codeplanner.agent.api import Agent, AgentRequest, AgentResult
67
from kai.reactive_codeplanner.agent.maven_compiler_fix.api import (
78
MavenCompilerAgentRequest,
89
MavenCompilerAgentResult,
910
)
1011

12+
logger = get_logger(__name__)
13+
1114

1215
class MavenCompilerAgent(Agent):
13-
system_message = SystemMessage(
14-
content="""
16+
system_message_template = Template(
17+
"""{{ background }}
1518
I will give you compiler errors and the offending line of code, and you will need to use the file to determine how to fix them. You should only use compiler errors to determine what to fix.
1619
1720
Make sure that the references to any changed types are kept.
@@ -54,6 +57,10 @@ def execute(self, ask: AgentRequest) -> AgentResult:
5457
if not isinstance(ask, MavenCompilerAgentRequest):
5558
return AgentResult()
5659

60+
system_message = SystemMessage(
61+
content=self.system_message_template.render(background=ask.background)
62+
)
63+
5764
line_of_code = ask.file_contents.split("\n")[ask.line_number]
5865

5966
compile_errors = f"Line of code: {line_of_code};\n{ask.message}"
@@ -62,7 +69,7 @@ def execute(self, ask: AgentRequest) -> AgentResult:
6269
)
6370

6471
ai_message = self.model_provider.invoke(
65-
[self.system_message, HumanMessage(content=content)]
72+
[system_message, HumanMessage(content=content)]
6673
)
6774

6875
resp = self.parse_llm_response(ai_message)

kai/reactive_codeplanner/agent/maven_compiler_fix/api.py

+1
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ def to_reflection_task(self) -> Optional[ReflectionTask]:
3838
reasoning=self.reasoning,
3939
updated_file_contents=self.updated_file_contents,
4040
original_file_contents=self.original_file,
41+
background="",
4142
)

kai/reactive_codeplanner/main.py

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ def main() -> None:
181181
task_manager.supply_result(result)
182182
except Exception as e:
183183
logger.error("Failed to supply result %s: %s", result, e)
184+
184185
supply_time = time.time() - start_supply_time
185186
logger.info("PERFORMANCE: %.6f seconds to supply result", supply_time)
186187

kai/reactive_codeplanner/task_manager/api.py

+10
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ def truncate(value: str, max_length: int = 30) -> str:
7676

7777
return f"{class_name}<" + ", ".join(field_strings) + ">"
7878

79+
def background(self) -> str:
80+
"""Used by Agents to provide context when solving child issues"""
81+
raise NotImplementedError
82+
7983
__repr__ = __str__
8084

8185

@@ -130,6 +134,12 @@ def __str__(self) -> str:
130134

131135
return f"{self.__class__.__name__}<loc={self.file}:{self.line}:{self.column}, message={self.message}>(priority={self.priority}({shadowed_priority}), depth={self.depth}, retries={self.retry_count})"
132136

137+
def background(self) -> str:
138+
"""Used by Agents to provide context when solving child issues"""
139+
if self.parent is None:
140+
return ""
141+
return self.oldest_ancestor().background()
142+
133143
__repr__ = __str__
134144

135145

kai/reactive_codeplanner/task_runner/analyzer_lsp/api.py

+36
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import itertools
12
from dataclasses import dataclass
3+
from functools import cached_property
24

35
from kai.analyzer_types import Incident, RuleSet, Violation
46
from kai.logging.logging import get_logger
@@ -29,6 +31,40 @@ def __str__(self) -> str:
2931

3032
return f"{self.__class__.__name__}<loc={self.file}:{self.line}:{self.column}, message={self.violation.description}>(priority={self.priority}({shadowed_priority}), depth={self.depth}, retries={self.retry_count})"
3133

34+
def background(self) -> str:
35+
if self.parent is not None:
36+
return self.oldest_ancestor().background()
37+
if self.children:
38+
return f"""You are a software developer who specializes in migrating from {" and ".join(self.sources)} to {" and ".join(self.targets)}
39+
You attempted to solve an issue in a repository you are migrating:
40+
41+
Location: {self.incident.uri}
42+
Message:
43+
{self.incident.message}
44+
45+
However your solution caused additional problems elsewhere in the repository, which you are now going to solve."""
46+
return ""
47+
48+
@cached_property
49+
def sources(self) -> list[str]:
50+
labels = set(
51+
itertools.chain(*[v.labels for v in self.ruleset.violations.values()])
52+
)
53+
source_key = "konveyor.io/source="
54+
return [
55+
label.replace(source_key, "") for label in labels if source_key in label
56+
]
57+
58+
@cached_property
59+
def targets(self) -> list[str]:
60+
labels = set(
61+
itertools.chain(*[v.labels for v in self.ruleset.violations.values()])
62+
)
63+
target_key = "konveyor.io/target="
64+
return [
65+
label.replace(target_key, "") for label in labels if target_key in label
66+
]
67+
3268
__repr__ = __str__
3369

3470
def fuzzy_equals(self, error2: Task, offset: int = 1) -> bool:

kai/reactive_codeplanner/task_runner/analyzer_lsp/task_runner.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def can_handle_task(self, task: Task) -> bool:
4242
"""Will determine if the task if a MavenCompilerError, and if we can handle these issues."""
4343
return isinstance(task, AnalyzerRuleViolation)
4444

45-
@tracer.start_as_current_span("analyzer_execute_task")
45+
@tracer.start_as_current_span("analyzer_execute_task") # type:ignore
4646
def execute_task(self, rcm: RepoContextManager, task: Task) -> TaskResult:
4747
"""This will be responsible for getting the full file from LLM and updating the file on disk"""
4848

@@ -58,6 +58,9 @@ def execute_task(self, rcm: RepoContextManager, task: Task) -> TaskResult:
5858
file_path=Path(os.path.abspath(task.file)),
5959
file_content=src_file_contents,
6060
incidents=[task.incident],
61+
background=task.background(),
62+
sources=task.sources,
63+
targets=task.targets,
6164
)
6265
result = self.agent.execute(agent_request)
6366

kai/reactive_codeplanner/task_runner/analyzer_lsp/validator.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def __init__(self, config: RpcClientConfig, analyzer: AnalyzerLSP) -> None:
4242
self.incident_selector = config.incident_selector or ""
4343
super().__init__(config)
4444

45-
@tracer.start_as_current_span("analyzer_run_validation")
45+
@tracer.start_as_current_span("analyzer_run_validation") # type:ignore
4646
def run(self, scoped_paths: Optional[list[Path]] = None) -> ValidationResult:
4747
logger.debug("Running analyzer-lsp")
4848

0 commit comments

Comments
 (0)