repolaunch 1.4.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.
- launch/__init__.py +8 -0
- launch/agent/__init__.py +0 -0
- launch/agent/action_parser.py +29 -0
- launch/agent/locate.py +136 -0
- launch/agent/organize/__init__.py +0 -0
- launch/agent/organize/parselog.py +341 -0
- launch/agent/organize/rebuild.py +352 -0
- launch/agent/organize/save.py +152 -0
- launch/agent/organize/testall.py +502 -0
- launch/agent/organize/testone.py +283 -0
- launch/agent/prompt.py +43 -0
- launch/agent/setup/__init__.py +0 -0
- launch/agent/setup/base_image.py +88 -0
- launch/agent/setup/save.py +115 -0
- launch/agent/setup/setup.py +326 -0
- launch/agent/setup/verify.py +219 -0
- launch/agent/state.py +209 -0
- launch/api.py +66 -0
- launch/core/entry.py +97 -0
- launch/core/platforms/__init__.py +0 -0
- launch/core/platforms/android.py +146 -0
- launch/core/platforms/base.py +324 -0
- launch/core/platforms/linux.py +358 -0
- launch/core/platforms/windows.py +310 -0
- launch/core/runtime.py +84 -0
- launch/core/workflow.py +154 -0
- launch/run.py +376 -0
- launch/scripts/__init__.py +0 -0
- launch/scripts/adjacent_commit_run.py +522 -0
- launch/scripts/clear_failed_instance.py +34 -0
- launch/scripts/clear_image.py +26 -0
- launch/scripts/collect.py +66 -0
- launch/scripts/gen_dockerfile.py +228 -0
- launch/scripts/parser.py +52 -0
- launch/scripts/upload_docker.py +94 -0
- launch/utilities/__init__.py +0 -0
- launch/utilities/config.py +85 -0
- launch/utilities/dockerfiles/windows_server_c_cpp/ltsc2025_cmake_ninja_vsbuildtools_cl_msbuild/Dockerfile +15 -0
- launch/utilities/dockerfiles/windows_server_c_cpp/ltsc_2019_cmake_ninja_only/Dockerfile +27 -0
- launch/utilities/dockerfiles/windows_server_c_cpp/ltsc_2022_cmake_ninja_only/Dockerfile +27 -0
- launch/utilities/dockerfiles/windows_server_c_cpp/ltsc_2025_vsbuildtools/Dockerfile +28 -0
- launch/utilities/dockerfiles/windows_server_nvm/Dockerfile +19 -0
- launch/utilities/dockerfiles/windows_server_rust/170/Dockerfile +23 -0
- launch/utilities/dockerfiles/windows_server_rust/175/Dockerfile +23 -0
- launch/utilities/dockerfiles/windows_server_rust/180/Dockerfile +23 -0
- launch/utilities/dockerfiles/windows_server_rust/185/Dockerfile +23 -0
- launch/utilities/dockerfiles/windows_server_rust/190/Dockerfile +23 -0
- launch/utilities/get_repo_structure.py +85 -0
- launch/utilities/language_handlers.py +669 -0
- launch/utilities/llm.py +247 -0
- launch/utilities/logger.py +74 -0
- launch/utilities/timemachine.py +270 -0
- launch/utilities/tools/str_replace_editor.py +289 -0
- launch/utilities/workspace.py +221 -0
- repolaunch-1.4.0.dist-info/METADATA +111 -0
- repolaunch-1.4.0.dist-info/RECORD +60 -0
- repolaunch-1.4.0.dist-info/WHEEL +5 -0
- repolaunch-1.4.0.dist-info/entry_points.txt +2 -0
- repolaunch-1.4.0.dist-info/licenses/LICENSE +21 -0
- repolaunch-1.4.0.dist-info/top_level.txt +1 -0
launch/__init__.py
ADDED
launch/agent/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Common action parsing utilities for agent interactions.
|
|
3
|
+
"""
|
|
4
|
+
import re
|
|
5
|
+
from typing import Optional, Any
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ActionParser(ABC):
|
|
10
|
+
"""Base class for parsing LLM responses into structured actions."""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def parse(self, response: str) -> Optional[Any]:
|
|
14
|
+
"""Parse response string into action object."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def extract_tag_content(response: str, tag: str) -> Optional[str]:
|
|
19
|
+
"""Extract content between XML-style tags."""
|
|
20
|
+
pattern = f"<{tag}>(.*?)</{tag}>"
|
|
21
|
+
match = re.search(pattern, response, re.DOTALL)
|
|
22
|
+
return match.group(1) if match else None
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def clean_response(response: str) -> str:
|
|
26
|
+
"""Remove reasoning tags from response if present."""
|
|
27
|
+
if "<think>" in response:
|
|
28
|
+
return response.split("</think>")[1]
|
|
29
|
+
return response
|
launch/agent/locate.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Repository analysis agent for locating environment setup documentation.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from langchain.schema import HumanMessage
|
|
7
|
+
|
|
8
|
+
from launch.agent.state import AgentState, auto_catch
|
|
9
|
+
from launch.utilities.get_repo_structure import view_repo_structure
|
|
10
|
+
from launch.utilities.llm import form_llm_cost_log, update_accumulative_cost
|
|
11
|
+
|
|
12
|
+
prompt = """Given this repository structure:
|
|
13
|
+
------ BEGIN REPOSITORY STRUCTURE ------
|
|
14
|
+
{structure}
|
|
15
|
+
------ END REPOSITORY STRUCTURE ------
|
|
16
|
+
|
|
17
|
+
List the most relevant files for setting up a development environment, including:
|
|
18
|
+
0. CI/CD configuration files
|
|
19
|
+
1. README files
|
|
20
|
+
2. Documentation
|
|
21
|
+
3. Installation guides
|
|
22
|
+
4. Development setup guides
|
|
23
|
+
|
|
24
|
+
Only list files that are critical for understanding project dependencies and setup requirements.
|
|
25
|
+
Format each file with its relative path (relative to project root) to be wrapped with tag <file> </file>, one per line."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
determine_prompt = """Given a file of the repository, determine if it is relevant for setting up a development environment for the repository or providing information about how to set up dev env (how to setup, install, test, etc.). This determines whether the file's content is fed to the LLM and helps it set up the environment.
|
|
29
|
+
|
|
30
|
+
### File:
|
|
31
|
+
{file}
|
|
32
|
+
|
|
33
|
+
### Reply with the following format:
|
|
34
|
+
|
|
35
|
+
<rel>Yes</rel>
|
|
36
|
+
|
|
37
|
+
or
|
|
38
|
+
|
|
39
|
+
<rel>No</rel>
|
|
40
|
+
|
|
41
|
+
Choose either Yes or No, Yes means this file IS relevant for setting up a dev env for the repository.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
THRESHOLD = 128 * 1000 * 2
|
|
45
|
+
|
|
46
|
+
@auto_catch
|
|
47
|
+
def locate_related_file(state: AgentState) -> dict:
|
|
48
|
+
"""
|
|
49
|
+
Analyze repository structure to identify files relevant for environment setup.
|
|
50
|
+
|
|
51
|
+
Uses LLM to scan repository structure and determine which files contain
|
|
52
|
+
information about dependencies, installation, and development setup.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
state (AgentState): Current agent state with repo structure
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
AgentState: Updated state with documentation content and related files
|
|
59
|
+
"""
|
|
60
|
+
llm = state["llm"]
|
|
61
|
+
cost = state["cost"]
|
|
62
|
+
logger = state["logger"]
|
|
63
|
+
repo_structure = state["repo_structure"]
|
|
64
|
+
|
|
65
|
+
locate_prompt = HumanMessage(
|
|
66
|
+
content=prompt.format(structure=repo_structure)
|
|
67
|
+
)
|
|
68
|
+
if len(locate_prompt.content) > THRESHOLD:
|
|
69
|
+
repo_structure = view_repo_structure(state["repo_root"], 2)
|
|
70
|
+
locate_prompt = HumanMessage(
|
|
71
|
+
content=prompt.format(structure=repo_structure)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
response = llm.invoke([locate_prompt])
|
|
75
|
+
update_accumulative_cost(cost["preparation"], response)
|
|
76
|
+
potential_files = [
|
|
77
|
+
line.split("<file>")[1].split("</file>")[0].strip()
|
|
78
|
+
for line in response.content.split("\n")
|
|
79
|
+
if line.strip() and "<file>" in line
|
|
80
|
+
]
|
|
81
|
+
potential_files = [
|
|
82
|
+
file
|
|
83
|
+
for file in potential_files
|
|
84
|
+
if os.path.exists(os.path.join(state["repo_root"], file))
|
|
85
|
+
]
|
|
86
|
+
potential_files = list(set(potential_files))
|
|
87
|
+
|
|
88
|
+
logger.info(f"Potential files: {potential_files} {form_llm_cost_log(response)}")
|
|
89
|
+
logger.info("Start determine relevance of these files...")
|
|
90
|
+
related_files = []
|
|
91
|
+
|
|
92
|
+
docs = "------ BEGIN RELATED FILES ------\n"
|
|
93
|
+
for file in potential_files:
|
|
94
|
+
path = os.path.join(state["repo_root"], file)
|
|
95
|
+
if not os.path.exists(path):
|
|
96
|
+
continue
|
|
97
|
+
if os.path.isdir(path):
|
|
98
|
+
continue
|
|
99
|
+
try:
|
|
100
|
+
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
|
101
|
+
content = f.read(THRESHOLD)
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.info(f"Error reading file {file}: {e}")
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
file_info = f"""------ START FILE {file} ------
|
|
107
|
+
{content}
|
|
108
|
+
------ END FILE {file} ------"""
|
|
109
|
+
determine_input = HumanMessage(content=determine_prompt.format(file=file_info))
|
|
110
|
+
|
|
111
|
+
response = llm.invoke([determine_input])
|
|
112
|
+
update_accumulative_cost(cost["preparation"], response)
|
|
113
|
+
logger.info(f"File: {file} - {response.content} {form_llm_cost_log(response)}")
|
|
114
|
+
if "<rel>Yes</rel>" in response.content:
|
|
115
|
+
docs += f"File: {file}\n```\n"
|
|
116
|
+
docs += content + "\n"
|
|
117
|
+
docs += "```\n"
|
|
118
|
+
related_files.append(file)
|
|
119
|
+
docs += "------ END RELATED FILES ------\n"
|
|
120
|
+
|
|
121
|
+
logger.info(f"Located related files: {related_files}")
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
"messages": [locate_prompt, response],
|
|
125
|
+
"docs": docs,
|
|
126
|
+
# We do not require the full repo structure later
|
|
127
|
+
"repo_structure": repo_structure,
|
|
128
|
+
"cost": cost,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
from launch.agent.state import AgentState
|
|
134
|
+
|
|
135
|
+
state = AgentState()
|
|
136
|
+
locate_related_file(state)
|
|
File without changes
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Log parser generation agent for improving test output parsing accuracy.
|
|
3
|
+
"""
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from langchain.schema import HumanMessage, SystemMessage
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from launch.agent.action_parser import ActionParser
|
|
11
|
+
from launch.agent.prompt import ReAct_prompt
|
|
12
|
+
from launch.agent.state import AgentState, auto_catch
|
|
13
|
+
from launch.scripts.parser import run_parser
|
|
14
|
+
from launch.utilities.llm import form_llm_cost_log, update_accumulative_cost
|
|
15
|
+
|
|
16
|
+
system_msg: str = """You are a developer specializing in test output analysis and parsing. Your task is to examine the test output, evaluate the current parser, and generate an improved, fully robust parser.
|
|
17
|
+
|
|
18
|
+
You have access to:
|
|
19
|
+
- **Raw test output** from the previous stage: {test_output}
|
|
20
|
+
- **Draft parser script** from the previous stage: {current_parser}
|
|
21
|
+
- **Draft parser results**: {current_results}
|
|
22
|
+
|
|
23
|
+
Your goal is to write a parser that it correctly extracts *every* test case and its status.
|
|
24
|
+
|
|
25
|
+
## Your Tasks:
|
|
26
|
+
1. **Analyze Draft Parser**: Evaluate how well the draft parser extracts test case statuses from the test output
|
|
27
|
+
2. **Identify Improvement Opportunities**: Look for:
|
|
28
|
+
- Missed test cases that should have been parsed
|
|
29
|
+
- Incorrectly parsed test cases
|
|
30
|
+
- Edge cases not handled properly
|
|
31
|
+
- Parsing patterns that could be more robust
|
|
32
|
+
|
|
33
|
+
3. **Generate Improved Parser**: Create a reliable parser that:
|
|
34
|
+
- Handles edge cases better
|
|
35
|
+
- Robust to output format variations
|
|
36
|
+
- Extracts granular test case information
|
|
37
|
+
- Make it as simple as possible while being effective
|
|
38
|
+
|
|
39
|
+
# REQUIRED PARSER OUTPUT
|
|
40
|
+
Your final parser MUST return **only** a dictionary in this exact form:
|
|
41
|
+
{{"test_case_name": "pass", "another_test": "fail", "third_test": "skip"}}
|
|
42
|
+
The test statuses can only be in {{"pass", "fail", "skip"}}. Any kinds of fail of error should be taken as 'fail'.
|
|
43
|
+
|
|
44
|
+
## Hints for testcase extraction
|
|
45
|
+
Refer to the patterns below to identify test cases and their statuses.
|
|
46
|
+
XML (Maven, Gradle, JUnit, TestNG):
|
|
47
|
+
- Look for `<testsuite>` and `<testcase>` elements.
|
|
48
|
+
- Status rules: `<failure>` → fail, `<error>` → fail, `<skipped>` → skip, otherwise pass.
|
|
49
|
+
pytest:
|
|
50
|
+
- Lines like: `file.py::test_name PASSED/FAILED/SKIPPED/ERROR`.
|
|
51
|
+
unittest:
|
|
52
|
+
- Patterns such as: `TestClass.test_method ... ok/FAIL/ERROR`.
|
|
53
|
+
Jest:
|
|
54
|
+
- Symbols: `✓` pass, `✕` fail, `○` skip
|
|
55
|
+
- Or keywords: `PASS` / `FAIL`.
|
|
56
|
+
Go test:
|
|
57
|
+
- Lines like: `--- PASS: TestName`, `--- FAIL: TestName`, `--- SKIP: TestName`.
|
|
58
|
+
Other frameworks:
|
|
59
|
+
- Look for consistent use of `PASS`, `FAIL`, or `SKIP` near test case names.
|
|
60
|
+
|
|
61
|
+
You need to finish this in {steps} steps.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ParseLogAction(BaseModel):
|
|
66
|
+
"""
|
|
67
|
+
Analyze: Analyze the current parser performance and test output patterns
|
|
68
|
+
<analyze>your analysis of current parser issues and improvement opportunities</analyze>
|
|
69
|
+
|
|
70
|
+
Parse: Generate an improved parser script
|
|
71
|
+
The input log argument is from the test log from the previous stage. The system automatically passes the test log as the input when you use the <python></python> action and gives you the result of the script.
|
|
72
|
+
<python>def parser(log: str) -> dict[str, str]:
|
|
73
|
+
# Your improved parser implementation
|
|
74
|
+
import re
|
|
75
|
+
results = {}
|
|
76
|
+
# ... parsing logic ...
|
|
77
|
+
return results</python>
|
|
78
|
+
|
|
79
|
+
Submit: Submit the final improved parser
|
|
80
|
+
<submit>final parser is ready and tested</submit>
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
action: Literal["analyze", "python", "submit"] = Field(
|
|
84
|
+
"analyze", description="The action type"
|
|
85
|
+
)
|
|
86
|
+
args: Any = Field(None, description="The action arguments")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ParseLogObservation(BaseModel):
|
|
90
|
+
"""Observation for the parse log action"""
|
|
91
|
+
|
|
92
|
+
content: str = Field("", description="The content of the observation")
|
|
93
|
+
is_stop: bool = Field(False, description="Whether stop the parse log loop")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ParseLogActionParser(ActionParser):
|
|
97
|
+
"""Parser for parse log agent actions."""
|
|
98
|
+
|
|
99
|
+
def parse(self, response: str) -> ParseLogAction | None:
|
|
100
|
+
"""Parse action from LLM response text."""
|
|
101
|
+
response = self.clean_response(response)
|
|
102
|
+
|
|
103
|
+
submit = self.extract_tag_content(response, "submit")
|
|
104
|
+
if submit:
|
|
105
|
+
return ParseLogAction(action="submit", args=submit)
|
|
106
|
+
|
|
107
|
+
script = self.extract_tag_content(response, "python")
|
|
108
|
+
if script:
|
|
109
|
+
return ParseLogAction(action="python", args=script)
|
|
110
|
+
|
|
111
|
+
analyze = self.extract_tag_content(response, "analyze")
|
|
112
|
+
if analyze:
|
|
113
|
+
return ParseLogAction(action="analyze", args=analyze)
|
|
114
|
+
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def parse_parselog_action(response: str) -> ParseLogAction | None:
|
|
119
|
+
"""Parse parse log action from LLM response text."""
|
|
120
|
+
parser = ParseLogActionParser()
|
|
121
|
+
return parser.parse(response)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
PARSELOG_CONVERSATION_WINDOW = 30
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@auto_catch
|
|
128
|
+
def generate_log_parser(state: AgentState, max_steps: int = 20) -> dict:
|
|
129
|
+
"""
|
|
130
|
+
Agent for generating improved log parsers based on test output analysis.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
max_steps (int): Maximum number of steps allowed
|
|
134
|
+
state (AgentState): Current agent state with test results
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
dict: Updated state with improved parser and results
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
improved_parser: str = ""
|
|
141
|
+
framework_detected: str = ""
|
|
142
|
+
analysis_result: str = ""
|
|
143
|
+
improved_test_status: dict[str, Literal['pass', 'fail', 'skip']] = {}
|
|
144
|
+
def observation_for_parselog_action(
|
|
145
|
+
state: AgentState, action: ParseLogAction | None
|
|
146
|
+
) -> ParseLogObservation:
|
|
147
|
+
"""Execute parse log action and return observation."""
|
|
148
|
+
nonlocal improved_parser, framework_detected, analysis_result, improved_test_status
|
|
149
|
+
|
|
150
|
+
if not action or not action.action:
|
|
151
|
+
content = f"""Please use the following format to make a valid action choice:\n{ParseLogAction.__doc__}"""
|
|
152
|
+
return ParseLogObservation(content=content, is_stop=False)
|
|
153
|
+
|
|
154
|
+
if action.action == "analyze":
|
|
155
|
+
analysis_result = action.args
|
|
156
|
+
content = f"Analysis completed: {action.args}\n\nNow try to write the parser."
|
|
157
|
+
return ParseLogObservation(content=content, is_stop=False)
|
|
158
|
+
|
|
159
|
+
elif action.action == "python":
|
|
160
|
+
improved_parser = action.args
|
|
161
|
+
|
|
162
|
+
if not improved_parser:
|
|
163
|
+
content = "No parser script available to test. Please create a parser first."
|
|
164
|
+
return ParseLogObservation(content=content, is_stop=False)
|
|
165
|
+
|
|
166
|
+
# Get test output from previous stage
|
|
167
|
+
test_output = state.get("test_output", "")
|
|
168
|
+
if not test_output:
|
|
169
|
+
# This should not happen as we store test_output in state at the beginning of generate_log_parser, but just in case...
|
|
170
|
+
raise ValueError("No test output available from previous stage to test against.")
|
|
171
|
+
|
|
172
|
+
# Test the improved parser
|
|
173
|
+
try:
|
|
174
|
+
result = run_parser(improved_parser, test_output)
|
|
175
|
+
if not isinstance(result, dict):
|
|
176
|
+
truncated_result = str(result)
|
|
177
|
+
if len(truncated_result) > 40000:
|
|
178
|
+
truncated_result = truncated_result[:40000] + "\n...result truncated due to length...\n"
|
|
179
|
+
content = (
|
|
180
|
+
f"Your python parser script must return a dict[str, Literal['pass', 'fail', 'skip']]."
|
|
181
|
+
f"However, it produced type {type(result).__name__}. The value or error your parser returned was:\n{truncated_result}\n"
|
|
182
|
+
"If the above is a traceback, fix the parser so it does not raise. "
|
|
183
|
+
"Note: the log passed into your parser is the raw stdout of your last command and may include the echoed command line "
|
|
184
|
+
"and the shell prompt, so your parser should be able to strip/ignore non-report lines.\n"
|
|
185
|
+
"Now adjust your parser script to make sure it returns the required format."
|
|
186
|
+
)
|
|
187
|
+
return ParseLogObservation(content=content, is_stop=False)
|
|
188
|
+
improved_test_status = result
|
|
189
|
+
truncated_result = json.dumps(result, indent=2)
|
|
190
|
+
if len(truncated_result) > 10000:
|
|
191
|
+
truncated_result = truncated_result[:10000] + "\n...result truncated due to length..."
|
|
192
|
+
|
|
193
|
+
# Compare with original results if available
|
|
194
|
+
original_results = state.get("test_status", {})
|
|
195
|
+
if original_results:
|
|
196
|
+
new_count = len(result)
|
|
197
|
+
old_count = len(original_results)
|
|
198
|
+
content = f"""Test results for improved parser:
|
|
199
|
+
{truncated_result}
|
|
200
|
+
|
|
201
|
+
Comparison with original parser:
|
|
202
|
+
- Original parser found: {old_count} test cases
|
|
203
|
+
- Improved parser found: {new_count} test cases
|
|
204
|
+
- Difference: {new_count - old_count} test cases
|
|
205
|
+
|
|
206
|
+
Please analyze if this is an improvement and submit if satisfied."""
|
|
207
|
+
else:
|
|
208
|
+
content = f"""Test results for improved parser:
|
|
209
|
+
{truncated_result}
|
|
210
|
+
|
|
211
|
+
Parser executed successfully. Please analyze the results and submit if satisfied."""
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
content = f"Error testing parser: {str(e)}\nPlease fix the parser and try again."
|
|
215
|
+
|
|
216
|
+
return ParseLogObservation(content=content, is_stop=False)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
elif action.action == "submit":
|
|
220
|
+
if not improved_parser:
|
|
221
|
+
content = "No improved parser available to submit. Please create a parser with <python></python> action first."
|
|
222
|
+
return ParseLogObservation(content=content, is_stop=False)
|
|
223
|
+
|
|
224
|
+
if not improved_test_status:
|
|
225
|
+
content = f"Your last parser did not return any test case: {improved_test_status}. Please adjust your parser to make it able to extract test case names and their statuses from test log with <python></python> action."
|
|
226
|
+
return ParseLogObservation(content=content, is_stop=False)
|
|
227
|
+
|
|
228
|
+
return ParseLogObservation(content=action.args, is_stop=True)
|
|
229
|
+
|
|
230
|
+
return ParseLogObservation(content="Unknown action", is_stop=False)
|
|
231
|
+
|
|
232
|
+
if state["exception"]:
|
|
233
|
+
raise state["exception"]
|
|
234
|
+
|
|
235
|
+
session = state["session"]
|
|
236
|
+
llm = state["llm"]
|
|
237
|
+
cost = state["cost"]
|
|
238
|
+
logger = state["logger"]
|
|
239
|
+
|
|
240
|
+
# Get data from previous testall stage
|
|
241
|
+
test_output = ""
|
|
242
|
+
current_parser = ""
|
|
243
|
+
current_results = {}
|
|
244
|
+
|
|
245
|
+
# Rerun test commands and print commands to get fresh test output
|
|
246
|
+
test_commands = state.get("test_commands", [])
|
|
247
|
+
print_commands = state.get("print_commands", [])
|
|
248
|
+
|
|
249
|
+
if test_commands:
|
|
250
|
+
for cmd in test_commands:
|
|
251
|
+
logger.info(f"Rerunning test command: {cmd}")
|
|
252
|
+
session.send_command(cmd)
|
|
253
|
+
|
|
254
|
+
if print_commands:
|
|
255
|
+
for cmd in print_commands:
|
|
256
|
+
logger.info(f"Running print command: {cmd}")
|
|
257
|
+
result = session.send_command(cmd)
|
|
258
|
+
test_output += result.output
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
if "parser" in state:
|
|
262
|
+
current_parser = state.get("parser", "")
|
|
263
|
+
|
|
264
|
+
if "test_status" in state:
|
|
265
|
+
current_results = state.get("test_status", {})
|
|
266
|
+
|
|
267
|
+
logger.info("-" * 10 + "Start parse log conversation" + "-" * 10)
|
|
268
|
+
|
|
269
|
+
messages = [
|
|
270
|
+
SystemMessage(
|
|
271
|
+
system_msg.format(
|
|
272
|
+
test_output=test_output[:15000] + "..." if len(test_output) > 15000 else test_output,
|
|
273
|
+
current_parser=current_parser,
|
|
274
|
+
current_results=json.dumps(current_results, indent=2)[:2000] + "..." if len(json.dumps(current_results, indent=2)) > 2000 else json.dumps(current_results, indent=2),
|
|
275
|
+
steps=max_steps,
|
|
276
|
+
)
|
|
277
|
+
),
|
|
278
|
+
HumanMessage(
|
|
279
|
+
ReAct_prompt.format(
|
|
280
|
+
tools=ParseLogAction.__doc__,
|
|
281
|
+
project_structure=state.get("repo_structure", ""),
|
|
282
|
+
docs=state.get("docs", ""),
|
|
283
|
+
)
|
|
284
|
+
),
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
prefix_messages = len(messages)
|
|
288
|
+
step = 0
|
|
289
|
+
|
|
290
|
+
# Store test_output in state for testing
|
|
291
|
+
state["test_output"] = test_output
|
|
292
|
+
|
|
293
|
+
while step < max_steps:
|
|
294
|
+
|
|
295
|
+
step += 1
|
|
296
|
+
|
|
297
|
+
# Use conversation window to avoid context overflow
|
|
298
|
+
if len(messages) < PARSELOG_CONVERSATION_WINDOW + prefix_messages:
|
|
299
|
+
input_messages = messages
|
|
300
|
+
else:
|
|
301
|
+
input_messages = (
|
|
302
|
+
messages[:prefix_messages] + messages[-PARSELOG_CONVERSATION_WINDOW:]
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
response = llm.invoke(input_messages)
|
|
306
|
+
update_accumulative_cost(cost["organize"], response)
|
|
307
|
+
|
|
308
|
+
logger.info(f"\n{response.pretty_repr()}\n\n{form_llm_cost_log(response)}\n")
|
|
309
|
+
messages.append(response)
|
|
310
|
+
|
|
311
|
+
action = parse_parselog_action(response.content)
|
|
312
|
+
observation = observation_for_parselog_action(state, action)
|
|
313
|
+
|
|
314
|
+
if observation.is_stop:
|
|
315
|
+
answer = observation.content
|
|
316
|
+
break
|
|
317
|
+
|
|
318
|
+
message = HumanMessage(f"Observation:\n{observation.content}")
|
|
319
|
+
logger.info("\n" + message.pretty_repr())
|
|
320
|
+
messages.append(message)
|
|
321
|
+
|
|
322
|
+
logger.info("-" * 10 + "End parse log conversation" + "-" * 10)
|
|
323
|
+
|
|
324
|
+
# Use the final improved parser if success else keep the old test status if the new parser does not return any result
|
|
325
|
+
if improved_test_status:
|
|
326
|
+
final_test_status = improved_test_status
|
|
327
|
+
final_parser = improved_parser
|
|
328
|
+
else:
|
|
329
|
+
final_test_status = state.get("test_status", {})
|
|
330
|
+
final_parser = state.get("parser", "")
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
"messages": messages,
|
|
334
|
+
"parser": final_parser,
|
|
335
|
+
"framework_detected": framework_detected,
|
|
336
|
+
"analysis_result": analysis_result,
|
|
337
|
+
"test_status": final_test_status,
|
|
338
|
+
"success": bool(final_test_status and final_parser),
|
|
339
|
+
"test_output": test_output,
|
|
340
|
+
"cost": cost,
|
|
341
|
+
}
|