mirror of
https://github.com/Tokfinity/InfCode.git
synced 2026-02-13 13:42:45 +00:00
initial
This commit is contained in:
275
src/managers/loop/base.py
Normal file
275
src/managers/loop/base.py
Normal file
@@ -0,0 +1,275 @@
|
||||
from typing import Any, Dict, List
|
||||
import json
|
||||
from traceback import format_exc
|
||||
from src.managers.log.logger import Logger
|
||||
from src.managers.llm_api.api_manager import LLMAPIManager
|
||||
from src.managers.prompts.prompts_manager import PromptsManager
|
||||
from src.tools.base import (
|
||||
ToolExecutor,
|
||||
BASH_TOOL_NAME,
|
||||
STR_REPLACE_BASED_EDIT_TOOL_NAME,
|
||||
SEARCH_TOOL_NAME,
|
||||
SUBMIT_RESULT_TOOL_NAME,
|
||||
)
|
||||
from src.managers.loop.types import ToolStats, LLMUsage
|
||||
|
||||
|
||||
|
||||
class BaseLoop:
|
||||
def __init__(self, instance_id: str, instance_data: Dict[str, Any], logger: Logger, prompts_manager: PromptsManager | None, llm_manager: LLMAPIManager | None, tool_executor: ToolExecutor, config: Dict[str, Any] | None = None):
|
||||
self.instance_id = instance_id
|
||||
self.instance_data = instance_data
|
||||
self.logger = logger
|
||||
self.prompts_manager = prompts_manager
|
||||
self.llm_manager = llm_manager
|
||||
self.tool_executor = tool_executor
|
||||
self.config = config or {}
|
||||
self.component_name = self.__class__.__name__
|
||||
|
||||
|
||||
def _make_assistant(
|
||||
self, content: str | None, tool_calls: Any, messages: List[Dict[str, Any]]
|
||||
) -> bool:
|
||||
"""
|
||||
Construct an assistant message based on the current content and tool calls, and append it to the messages.
|
||||
"""
|
||||
safe_content = content or ""
|
||||
if not safe_content and not tool_calls:
|
||||
self.logger.warning(
|
||||
f"[{self.component_name}] Assistant returned an empty message with no tool calls; skipping this message and prompting to continue"
|
||||
)
|
||||
messages.append(
|
||||
{"role": "user", "content": "请继续分析问题并使用工具来解决问题。"}
|
||||
)
|
||||
return False
|
||||
assistant_message: Dict[str, Any] = {"role": "assistant"}
|
||||
if tool_calls and not safe_content:
|
||||
assistant_message["content"] = ""
|
||||
elif safe_content:
|
||||
assistant_message["content"] = safe_content
|
||||
if tool_calls:
|
||||
assistant_message["tool_calls"] = tool_calls
|
||||
messages.append(assistant_message)
|
||||
return True
|
||||
|
||||
def _make_tool_response(
|
||||
self, tool_results: List[Any], messages: List[Dict[str, Any]]
|
||||
) -> None:
|
||||
"""Convert tool execution results into standard tool messages (role=tool) and append them to the messages.
|
||||
|
||||
- Generate content per result: use prompts_manager.tool_response_prompts([{...}]) to produce the content
|
||||
- Set tool_call_id: prefer ToolResult.id; fallback to ToolResult.call_id
|
||||
"""
|
||||
if not tool_results:
|
||||
return
|
||||
for result in tool_results:
|
||||
single_dict = [
|
||||
{
|
||||
"name": getattr(result, "name", "unknown"),
|
||||
"success": getattr(result, "success", False),
|
||||
"result": getattr(result, "result", None) or "",
|
||||
"error": getattr(result, "error", None) or "",
|
||||
}
|
||||
]
|
||||
content_text = (
|
||||
self.prompts_manager.tool_response_prompts(single_dict)
|
||||
if self.prompts_manager
|
||||
else ""
|
||||
)
|
||||
tool_call_id = getattr(result, "id", None) or getattr(
|
||||
result, "call_id", None
|
||||
)
|
||||
messages.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"content": content_text,
|
||||
"tool_call_id": tool_call_id,
|
||||
}
|
||||
)
|
||||
|
||||
def _response_log(
|
||||
self, response: Any, first_content: str, first_tool_calls: Any, total_turns: int
|
||||
) -> None:
|
||||
"""notice log for the current turn's LLM output"""
|
||||
try:
|
||||
response_log: Dict[str, Any] = {}
|
||||
if hasattr(response, "usage") and response.usage:
|
||||
response_log["usage"] = {
|
||||
"prompt_tokens": getattr(response.usage, "prompt_tokens", None),
|
||||
"completion_tokens": getattr(response.usage, "completion_tokens", None),
|
||||
"total_tokens": getattr(response.usage, "total_tokens", None),
|
||||
}
|
||||
if hasattr(response, "choices") and response.choices:
|
||||
response_log["choice"] = {
|
||||
"message": {
|
||||
"content": first_content,
|
||||
"tool_calls": first_tool_calls,
|
||||
}
|
||||
}
|
||||
if response_log:
|
||||
self.logger.notice(
|
||||
f"[{self.component_name}] The {total_turns}th turn output: {json.dumps(response_log, ensure_ascii=False)}"
|
||||
)
|
||||
else:
|
||||
self.logger.notice(
|
||||
f"[{self.component_name}] The {total_turns}th turn output: {str(response)}"
|
||||
)
|
||||
except Exception:
|
||||
self.logger.notice(
|
||||
f"[{self.component_name}] 第 {total_turns} 轮: LLM 输出序列化失败,使用字符串表示: {str(response)}, traceback: {format_exc()}."
|
||||
)
|
||||
|
||||
def _debug_messages(
|
||||
self, turn: int, messages: List[Dict[str, Any]], prefix_len: int = 300
|
||||
) -> None:
|
||||
"""debug log for the messages to be sent to the model"""
|
||||
try:
|
||||
self.logger.debug(f"[{self.component_name}] msg:")
|
||||
recent_messages = messages[-2:] if len(messages) > 2 else messages
|
||||
base_index = len(messages) - len(recent_messages)
|
||||
for offset, msg in enumerate[Dict[str, Any]](recent_messages):
|
||||
idx = base_index + offset
|
||||
role = msg.get("role")
|
||||
content = msg.get("content")
|
||||
content_str = content if isinstance(content, str) else ""
|
||||
preview = content_str[:prefix_len]
|
||||
content_len = len(content_str)
|
||||
extra = ""
|
||||
if role == "assistant":
|
||||
tool_calls = msg.get("tool_calls")
|
||||
has_tool = tool_calls is not None and tool_calls != []
|
||||
try:
|
||||
tool_calls_json = json.dumps(tool_calls, ensure_ascii=False)
|
||||
except Exception:
|
||||
self.logger.warning(
|
||||
f"[{self.component_name}] In debug_messages function, fail: {format_exc()}, tool calls: {tool_calls}."
|
||||
)
|
||||
tool_calls_json = str(tool_calls)
|
||||
extra = f", has_tool_calls={has_tool}, tool_calls={tool_calls_json}"
|
||||
elif role == "tool":
|
||||
tool_call_id = msg.get("tool_call_id")
|
||||
extra = f", tool_call_id={tool_call_id}"
|
||||
self.logger.debug(
|
||||
f"[{self.component_name}] {turn+1}th, msg#{idx}: role={role}, content_len={content_len}, content_preview={json.dumps(preview, ensure_ascii=False)}{extra}"
|
||||
)
|
||||
except Exception:
|
||||
self.logger.warning(
|
||||
f"[{self.component_name}] In debug_messages function, fail msg: {format_exc()}."
|
||||
)
|
||||
|
||||
def _debug_last_message(
|
||||
self, turn: int, messages: List[Dict[str, Any]], prefix_len: int = 300
|
||||
) -> None:
|
||||
"""debug last turn msg"""
|
||||
try:
|
||||
if not messages:
|
||||
return
|
||||
last_assistant_idx = None
|
||||
for i in range(len(messages) - 1, -1, -1):
|
||||
if messages[i].get("role") == "assistant":
|
||||
last_assistant_idx = i
|
||||
break
|
||||
if last_assistant_idx is None:
|
||||
return
|
||||
msg = messages[last_assistant_idx]
|
||||
content = msg.get("content")
|
||||
content_str = content if isinstance(content, str) else ""
|
||||
preview = content_str[:prefix_len]
|
||||
content_len = len(content_str)
|
||||
tool_calls = msg.get("tool_calls")
|
||||
has_tool = tool_calls is not None and tool_calls != []
|
||||
try:
|
||||
tool_calls_json = json.dumps(tool_calls, ensure_ascii=False)
|
||||
except Exception:
|
||||
self.logger.warning(
|
||||
f"[{self.component_name}] In debug_last_message function, fail: {format_exc()}, tool calls: {tool_calls}."
|
||||
)
|
||||
tool_calls_json = str(tool_calls)
|
||||
self.logger.debug(
|
||||
f"[{self.component_name}] {turn+1}th turn, output_preview: role=assistant, content_len={content_len}, content_preview={json.dumps(preview, ensure_ascii=False)}, has_tool_calls={has_tool}, tool_calls={tool_calls_json}"
|
||||
)
|
||||
except Exception:
|
||||
self.logger.warning(
|
||||
f"[{self.component_name}] In debug_last_message function, last turn fail: {format_exc()}."
|
||||
)
|
||||
|
||||
def _debug_tools(self, tools: List[Dict[str, Any]]) -> None:
|
||||
"""debug tools msg"""
|
||||
try:
|
||||
self.logger.debug(f"[{self.component_name}] tools num: {len(tools)}")
|
||||
for i, tool in enumerate(tools):
|
||||
try:
|
||||
tool_json = json.dumps(tool, ensure_ascii=False)
|
||||
self.logger.debug(f"[{self.component_name}] tool #{i+1}: {tool_json}")
|
||||
except Exception:
|
||||
self.logger.debug(
|
||||
f"[{self.component_name}] tool #{i+1} fail: {format_exc()}, string: {str(tool)}."
|
||||
)
|
||||
except Exception:
|
||||
try:
|
||||
self.logger.warning(
|
||||
f"[{self.component_name}] fail; traceback: {format_exc()}."
|
||||
)
|
||||
self.logger.warning(f"[{self.component_name}] tools string: {str(tools)}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _get_tools(self) -> List[Dict[str, Any]]:
|
||||
pass
|
||||
|
||||
def _is_bash_tool(self, tool_name: str) -> bool:
|
||||
return BASH_TOOL_NAME in tool_name
|
||||
|
||||
def _is_edit_tool(self, tool_name: str) -> bool:
|
||||
return "edit" in tool_name or "str_replace" in tool_name or STR_REPLACE_BASED_EDIT_TOOL_NAME in tool_name
|
||||
|
||||
def _is_search_tool(self, tool_name: str) -> bool:
|
||||
return SEARCH_TOOL_NAME in tool_name or "search" in tool_name
|
||||
|
||||
def _is_submit_result_tool(self, tool_name: str) -> bool:
|
||||
return SUBMIT_RESULT_TOOL_NAME in tool_name
|
||||
|
||||
def _update_usage(self, response: Any, usage_stats: LLMUsage) -> None:
|
||||
if hasattr(response, "usage") and response.usage:
|
||||
usage_stats.prompt_tokens += int(getattr(response.usage, "prompt_tokens", 0) or 0)
|
||||
usage_stats.completion_tokens += int(
|
||||
getattr(response.usage, "completion_tokens", 0) or 0
|
||||
)
|
||||
usage_stats.total_tokens += int(getattr(response.usage, "total_tokens", 0) or 0)
|
||||
|
||||
def _init_usage_stats(self) -> LLMUsage:
|
||||
return LLMUsage()
|
||||
|
||||
def _init_tools_stats(self) -> ToolStats:
|
||||
return ToolStats()
|
||||
|
||||
def _update_tool_call_statistic(
|
||||
self, tool_results: List[Any], tool_stats: ToolStats
|
||||
) -> None:
|
||||
for result in tool_results:
|
||||
try:
|
||||
tool_name = getattr(result, "name", "")
|
||||
tool_name = tool_name.lower() if isinstance(tool_name, str) else ""
|
||||
success = bool(getattr(result, "success", False))
|
||||
|
||||
if self._is_bash_tool(tool_name):
|
||||
tool_stats.bash["count"] += 1
|
||||
if not success:
|
||||
tool_stats.bash["failed"] += 1
|
||||
|
||||
elif self._is_edit_tool(tool_name):
|
||||
tool_stats.edit["count"] += 1
|
||||
if not success:
|
||||
tool_stats.edit["failed"] += 1
|
||||
|
||||
elif self._is_search_tool(tool_name):
|
||||
tool_stats.search["count"] += 1
|
||||
if not success:
|
||||
tool_stats.search["failed"] += 1
|
||||
|
||||
elif self._is_submit_result_tool(tool_name):
|
||||
tool_stats.submit_result["count"] += 1
|
||||
if not success:
|
||||
tool_stats.submit_result["failed"] += 1
|
||||
except Exception:
|
||||
continue
|
||||
339
src/managers/loop/patch_generator.py
Normal file
339
src/managers/loop/patch_generator.py
Normal file
@@ -0,0 +1,339 @@
|
||||
from typing import Any, Dict, List
|
||||
import json
|
||||
from traceback import format_exc
|
||||
from src.managers.log.logger import Logger
|
||||
from src.managers.llm_api.api_manager import LLMAPIManager
|
||||
from src.managers.prompts.prompts_manager import PromptsManager
|
||||
from src.managers.loop.base import BaseLoop
|
||||
from src.tools.base import (
|
||||
ToolExecutor,
|
||||
ToolResult,
|
||||
SubmitToolResult,
|
||||
BASH_TOOL_NAME,
|
||||
STR_REPLACE_BASED_EDIT_TOOL_NAME,
|
||||
SEARCH_TOOL_NAME,
|
||||
SUBMIT_RESULT_TOOL_NAME,
|
||||
)
|
||||
|
||||
|
||||
class PatchGenerator(BaseLoop):
|
||||
def __init__(
|
||||
self,
|
||||
instance_id: str,
|
||||
instance_data: Dict[str, Any],
|
||||
logger: Logger,
|
||||
prompts_manager: PromptsManager | None,
|
||||
llm_manager: LLMAPIManager | None,
|
||||
tool_executor: ToolExecutor,
|
||||
config: Dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
super().__init__(instance_id, instance_data, logger, prompts_manager, llm_manager, tool_executor, config)
|
||||
|
||||
|
||||
async def _submit_all_tool_calls(
|
||||
self, other_tool_calls: List[Dict[str, Any]]
|
||||
) -> List[Any]:
|
||||
"""execute tool calls, return tool execution results list"""
|
||||
if not other_tool_calls:
|
||||
return []
|
||||
from src.tools.base import ToolCall
|
||||
|
||||
tool_call_objects = []
|
||||
for tool_call_dict in other_tool_calls:
|
||||
raw_args = tool_call_dict.get("function", {}).get("arguments", {})
|
||||
parsed_args = raw_args
|
||||
if isinstance(raw_args, str):
|
||||
try:
|
||||
parsed_args = json.loads(raw_args)
|
||||
except Exception as e:
|
||||
self.logger.warning(f"[{self.component_name}] In _submit_all_tool_calls function, fail: {e}, traceback: {format_exc()}, args: {raw_args}.")
|
||||
parsed_args = {}
|
||||
tool_call_obj = ToolCall(
|
||||
name=tool_call_dict.get("function", {}).get("name", ""),
|
||||
call_id=tool_call_dict.get("id", ""),
|
||||
arguments=parsed_args,
|
||||
id=tool_call_dict.get("id", ""),
|
||||
)
|
||||
tool_call_objects.append(tool_call_obj)
|
||||
return await self.tool_executor.container_sequential_tool_call(
|
||||
tool_call_objects
|
||||
)
|
||||
|
||||
def _process_submit_result_tool_result(
|
||||
self,
|
||||
submit_result: ToolResult,
|
||||
golden_patch: List[Dict[str, Any]],
|
||||
) -> None:
|
||||
"""process submit_result tool call, fill golden_patch and log"""
|
||||
if not submit_result.success or not submit_result.result:
|
||||
self.logger.warning(f"[{self.component_name}] submit_result failed and no result.")
|
||||
return
|
||||
|
||||
try:
|
||||
submit_tool_result = SubmitToolResult.from_string(submit_result.result)
|
||||
|
||||
if submit_tool_result.output:
|
||||
patch_info = {
|
||||
"patch_content": submit_tool_result.output,
|
||||
"test_status": submit_tool_result.test_status,
|
||||
"reasoning": submit_tool_result.reasoning,
|
||||
}
|
||||
golden_patch.clear()
|
||||
golden_patch.append(patch_info)
|
||||
self.logger.info(
|
||||
f"[{self.component_name}] patch len: {len(submit_tool_result.output)}."
|
||||
)
|
||||
self.logger.info(
|
||||
f"[{self.component_name}] test status: {submit_tool_result.test_status}."
|
||||
)
|
||||
self.logger.info(
|
||||
f"[{self.component_name}] reasoning: {submit_tool_result.reasoning[:100]}..."
|
||||
)
|
||||
else:
|
||||
self.logger.warning(
|
||||
f"[{self.component_name}] submit_result success but no patch content."
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.error(f"[{self.component_name}] parse submit_result result fail: {e}, traceback: {format_exc()}.")
|
||||
|
||||
def _get_tools(self) -> List[Dict[str, Any]]:
|
||||
tools = []
|
||||
#use_openai_format = self._should_use_openai_format()
|
||||
use_openai_format = True
|
||||
|
||||
for tool in self.tool_executor.tools.values():
|
||||
if use_openai_format:
|
||||
tool_def = tool._definition_for_openai_fmt()
|
||||
else:
|
||||
tool_def = tool._definition_for_claude_fmt()
|
||||
tools.append(tool_def)
|
||||
|
||||
return tools
|
||||
|
||||
def _should_use_openai_format(self) -> bool:
|
||||
|
||||
if not self.llm_manager or not hasattr(self.llm_manager, "get_model_name"):
|
||||
return True # openAI format by default
|
||||
|
||||
model_name = self.llm_manager.get_model_name().lower()
|
||||
|
||||
return "claude" not in model_name
|
||||
|
||||
def _get_issue_prompt(self) -> str:
|
||||
"""generate issue prompt based on instance data"""
|
||||
if not self.prompts_manager:
|
||||
self.logger.warning("PromptsManager not initialized, cannot generate issue prompt.")
|
||||
return ""
|
||||
|
||||
#instance_id = self.instance_data.get("instance_id", "")
|
||||
#repo = self.instance_data.get("repo", "")
|
||||
created_at = self.instance_data.get("created_at", "")
|
||||
base_commit = self.instance_data.get("base_commit", "")
|
||||
environment_setup_commit = self.instance_data.get(
|
||||
"environment_setup_commit", ""
|
||||
)
|
||||
version = self.instance_data.get("version", "")
|
||||
problem_statement = self.instance_data.get("problem_statement", "")
|
||||
difficulty = self.instance_data.get("difficulty", "")
|
||||
|
||||
return self.prompts_manager.format_issue_prompt(
|
||||
created_at=created_at,
|
||||
base_commit=base_commit,
|
||||
environment_setup_commit=environment_setup_commit,
|
||||
version=version,
|
||||
problem_statement=problem_statement,
|
||||
difficulty=difficulty,
|
||||
)
|
||||
|
||||
async def _generate_patch(self) -> Dict[str, Any] | None:
|
||||
"""main loop logic for generating candidate patch"""
|
||||
|
||||
usage_stats = self._init_usage_stats()
|
||||
tool_stats = self._init_tools_stats()
|
||||
|
||||
if not self.llm_manager or not self.prompts_manager:
|
||||
self.logger.error(f"[{self.component_name}] LLM manager or prompts manager not initialized.")
|
||||
return {
|
||||
"success": False,
|
||||
"golden_patch": [],
|
||||
"llm_usage": usage_stats.to_dict(),
|
||||
"tool_stats": tool_stats.to_dict(),
|
||||
"total_turns": 0,
|
||||
}
|
||||
|
||||
tools = self._get_tools()
|
||||
|
||||
self._debug_tools(tools)
|
||||
|
||||
root_path = self.config.get("builder", {}).get("repo_root_path", "")
|
||||
max_turn = (
|
||||
self.config.get("runner", {}).get("generator_loop", {}).get("max_turn", 10)
|
||||
)
|
||||
temperature = (
|
||||
self.config.get("runner", {})
|
||||
.get("generator_loop", {})
|
||||
.get("temperature", 0.2)
|
||||
)
|
||||
|
||||
issue_prompt = self._get_issue_prompt()
|
||||
user_prompt = self.prompts_manager.get_generator_user(root_path, issue_prompt)
|
||||
system_prompt = self.prompts_manager.get_generator_system(root_path)
|
||||
|
||||
|
||||
total_turns = 0
|
||||
golden_patch = []
|
||||
|
||||
try:
|
||||
self.logger.info(
|
||||
f"[{self.component_name}] {self.instance_id}: start generating candidate patch, max turn: {max_turn}"
|
||||
)
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_prompt},
|
||||
]
|
||||
|
||||
self.logger.notice(
|
||||
f"[{self.component_name}]: {json.dumps(messages[0], ensure_ascii=False)}"
|
||||
)
|
||||
self.logger.notice(
|
||||
f"[{self.component_name}]: {json.dumps(messages[1], ensure_ascii=False)}"
|
||||
)
|
||||
|
||||
for turn in range(max_turn):
|
||||
total_turns = turn + 1
|
||||
self.logger.info(f"[{self.component_name}] The {total_turns}th turn started.")
|
||||
|
||||
try:
|
||||
current_input_msg = messages[-1] if messages else None
|
||||
if current_input_msg is not None:
|
||||
self.logger.notice(
|
||||
f"[{self.component_name}] The {total_turns}th turn input: {json.dumps(current_input_msg, ensure_ascii=False)}"
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
f"[{self.component_name}] {total_turns}th turn: LLM input fail: {messages[-1] if messages else None}, error: {e}, traceback: {format_exc()}."
|
||||
)
|
||||
|
||||
self._debug_messages(turn, messages)
|
||||
|
||||
response = self.llm_manager.chat(
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
tool_choice="auto",
|
||||
temperature=temperature,
|
||||
)
|
||||
|
||||
first_content: str = ""
|
||||
first_tool_calls: Any = None
|
||||
if hasattr(response, "choices") and response.choices:
|
||||
ch0 = response.choices[0]
|
||||
first_content = (
|
||||
getattr(getattr(ch0, "message", None), "content", None) or ""
|
||||
)
|
||||
first_tool_calls = getattr(
|
||||
getattr(ch0, "message", None), "tool_calls", None
|
||||
)
|
||||
|
||||
self._response_log(
|
||||
response, first_content, first_tool_calls, total_turns
|
||||
)
|
||||
|
||||
self._update_usage(response, usage_stats)
|
||||
|
||||
if hasattr(response, "choices") and response.choices:
|
||||
content = first_content
|
||||
tool_calls = first_tool_calls
|
||||
|
||||
if not self._make_assistant(content, tool_calls, messages):
|
||||
continue
|
||||
|
||||
if tool_calls:
|
||||
self.logger.info(
|
||||
f"[{self.component_name}] {total_turns}th turn: call {len(tool_calls)} tools."
|
||||
)
|
||||
|
||||
tool_results = await self._submit_all_tool_calls(tool_calls)
|
||||
|
||||
self._update_tool_call_statistic(tool_results, tool_stats)
|
||||
|
||||
if tool_results:
|
||||
submit_result = None
|
||||
other_tool_results = []
|
||||
|
||||
for tool_result in tool_results:
|
||||
tool_name = getattr(tool_result, "name", "")
|
||||
if tool_name == SUBMIT_RESULT_TOOL_NAME:
|
||||
submit_result = tool_result
|
||||
else:
|
||||
other_tool_results.append(tool_result)
|
||||
|
||||
if submit_result:
|
||||
self.logger.debug(
|
||||
f"[{self.component_name}] {total_turns}th turn: got submit_result tool call."
|
||||
)
|
||||
self.logger.debug(f"[{self.component_name}] {total_turns}th turn: submit_result result: {submit_result}")
|
||||
|
||||
self._process_submit_result_tool_result(
|
||||
submit_result, golden_patch
|
||||
)
|
||||
|
||||
self._debug_last_message(turn, messages)
|
||||
break
|
||||
|
||||
if other_tool_results:
|
||||
self._make_tool_response(other_tool_results, messages)
|
||||
|
||||
else:
|
||||
messages.append(
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请继续分析问题并使用工具来解决问题。",
|
||||
}
|
||||
)
|
||||
|
||||
self.logger.debug(f"[{self.component_name}] final golden_patch: {golden_patch}")
|
||||
|
||||
success = (
|
||||
len(golden_patch) > 0 and golden_patch[0].get("patch_content", "") != ""
|
||||
)
|
||||
|
||||
self.logger.info(
|
||||
f"[{self.component_name}] status={success}, total_turns={total_turns}, tools_stats={tool_stats}"
|
||||
)
|
||||
|
||||
result_payload = {
|
||||
"success": success,
|
||||
"golden_patch": golden_patch,
|
||||
"llm_usage": usage_stats.to_dict(),
|
||||
"tool_stats": tool_stats.to_dict(),
|
||||
"total_turns": total_turns,
|
||||
}
|
||||
try:
|
||||
self.logger.notice(
|
||||
f"[{self.component_name}] final output: {json.dumps(result_payload, ensure_ascii=False)}"
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
f"[{self.component_name}] output: {str(result_payload)}, error: {e}, traceback: {format_exc()}."
|
||||
)
|
||||
return result_payload
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"[{self.component_name}] fail: {e}, traceback: {format_exc()}.")
|
||||
result_payload = {
|
||||
"success": False,
|
||||
"golden_patch": [],
|
||||
"llm_usage": usage_stats.to_dict(),
|
||||
"tool_stats": tool_stats.to_dict(),
|
||||
"total_turns": total_turns,
|
||||
}
|
||||
try:
|
||||
self.logger.notice(
|
||||
f"[{self.component_name}] 最终返回数据(失败): {json.dumps(result_payload, ensure_ascii=False)}"
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.notice(
|
||||
f"[{self.component_name}] 最终返回数据(失败, 字符串回退): {str(result_payload)}, error: {e}, traceback: {format_exc()}."
|
||||
)
|
||||
return result_payload
|
||||
338
src/managers/loop/patch_selector.py
Normal file
338
src/managers/loop/patch_selector.py
Normal file
@@ -0,0 +1,338 @@
|
||||
from typing import Any, Dict, List
|
||||
import json
|
||||
from traceback import format_exc
|
||||
from src.managers.log.logger import Logger
|
||||
from src.managers.llm_api.api_manager import LLMAPIManager
|
||||
from src.managers.prompts.prompts_manager import PromptsManager
|
||||
from src.managers.loop.types import GeneratorResult, SelectorResult, LLMUsage, ToolStats, PatchInfo
|
||||
from src.tools.base import ToolExecutor, ToolCall, ToolResult
|
||||
from src.managers.loop.base import BaseLoop
|
||||
|
||||
SELECTOR_SUBMIT_TOOL_NAME = "submit_result"
|
||||
|
||||
class PatchSelector(BaseLoop):
|
||||
def __init__(
|
||||
self,
|
||||
instance_id: str,
|
||||
instance_data: Dict[str, Any],
|
||||
logger: Logger,
|
||||
prompts_manager: PromptsManager | None,
|
||||
llm_manager: LLMAPIManager | None,
|
||||
tool_executor: ToolExecutor,
|
||||
config: Dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
super().__init__(instance_id, instance_data, logger, prompts_manager, llm_manager, tool_executor, config)
|
||||
|
||||
def _get_submit_result_tool_name(self):
|
||||
return SELECTOR_SUBMIT_TOOL_NAME
|
||||
|
||||
def _definition_for_submit_tool(self, use_openai_format: bool) -> Dict[str, Any]:
|
||||
"""submit_result tool"""
|
||||
if use_openai_format:
|
||||
return {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": self._get_submit_result_tool_name(),
|
||||
"description": "Submit the final selected patch index and reasoning.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"index": {
|
||||
"type": "integer",
|
||||
"description": "The chosen patch index (0-based).",
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "Detailed reasoning for the selection.",
|
||||
},
|
||||
},
|
||||
"required": ["index", "reason"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": self._get_submit_result_tool_name(),
|
||||
"description": "Submit the final selected patch index and reasoning.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"index": {
|
||||
"type": "integer",
|
||||
"description": "The chosen patch index (0-based).",
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "Detailed reasoning for the selection.",
|
||||
},
|
||||
},
|
||||
"required": ["index", "reason"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def _build_user_prompt(self, candidates: List[GeneratorResult], root_path: str) -> str:
|
||||
|
||||
if not self.prompts_manager:
|
||||
return ""
|
||||
return self.prompts_manager.get_selector_user(self.instance_data, candidates, root_path)
|
||||
|
||||
def _get_system_prompt(self, patches_count: int, root_path: str) -> str:
|
||||
if not self.prompts_manager:
|
||||
return ""
|
||||
return self.prompts_manager.get_selector_system(patches_count, root_path)
|
||||
|
||||
def _get_tools(self) -> List[Dict[str, Any]]:
|
||||
|
||||
tool_defs: List[Dict[str, Any]] = []
|
||||
try:
|
||||
for tool in self.tool_executor.tools.values():
|
||||
try:
|
||||
tool_defs.append(tool._definition_for_openai_fmt())
|
||||
except Exception:
|
||||
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
tool_defs.append(self._definition_for_submit_tool(True))
|
||||
return tool_defs
|
||||
|
||||
def _extract_submit_choice(self, tool_call: Dict[str, Any]) -> Dict[str, Any] | None:
|
||||
|
||||
if not tool_call:
|
||||
return None
|
||||
fn = tool_call.get("function", {})
|
||||
if fn.get("name") != self._get_submit_result_tool_name():
|
||||
return None
|
||||
raw_args = fn.get("arguments", {})
|
||||
try:
|
||||
args = json.loads(raw_args) if isinstance(raw_args, str) else raw_args
|
||||
except Exception:
|
||||
args = {}
|
||||
index = args.get("index")
|
||||
reason = args.get("reason")
|
||||
if isinstance(index, int) and index >= 0:
|
||||
return {"index": index, "reason": reason or ""}
|
||||
return None
|
||||
|
||||
async def _submit_other_tool_calls(
|
||||
self, tool_calls: List[Dict[str, Any]]
|
||||
) -> List[ToolResult]:
|
||||
|
||||
if not tool_calls:
|
||||
return []
|
||||
to_run: List[ToolCall] = []
|
||||
for tool_call_dict in tool_calls:
|
||||
fn = tool_call_dict.get("function", {})
|
||||
name = fn.get("name", "")
|
||||
if name == SELECTOR_SUBMIT_TOOL_NAME:
|
||||
continue
|
||||
raw_args = fn.get("arguments", {})
|
||||
parsed_args = raw_args
|
||||
if isinstance(raw_args, str):
|
||||
try:
|
||||
parsed_args = json.loads(raw_args)
|
||||
except Exception:
|
||||
parsed_args = {}
|
||||
to_run.append(
|
||||
ToolCall(
|
||||
name=name,
|
||||
call_id=tool_call_dict.get("id", ""),
|
||||
arguments=parsed_args,
|
||||
id=tool_call_dict.get("id", ""),
|
||||
)
|
||||
)
|
||||
if not to_run:
|
||||
return []
|
||||
results: List[ToolResult] = await self.tool_executor.container_sequential_tool_call(to_run)
|
||||
return results
|
||||
|
||||
async def _select_patch(self, candidates: List[GeneratorResult]) -> SelectorResult:
|
||||
|
||||
if not candidates:
|
||||
raise ValueError("No candidates provided")
|
||||
if not self.llm_manager:
|
||||
raise ValueError("LLM manager is not initialized")
|
||||
|
||||
|
||||
tools = self._get_tools()
|
||||
|
||||
self._debug_tools(tools)
|
||||
|
||||
root_path = self.config.get("builder", {}).get("repo_root_path", "")
|
||||
system_prompt = self._get_system_prompt(len(candidates), root_path)
|
||||
user_prompt = self._build_user_prompt(candidates, root_path)
|
||||
|
||||
messages: List[Dict[str, Any]] = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_prompt},
|
||||
]
|
||||
|
||||
|
||||
try:
|
||||
self.logger.notice(
|
||||
f"[{self.component_name}]: {json.dumps(messages[0], ensure_ascii=False)}"
|
||||
)
|
||||
self.logger.notice(
|
||||
f"[{self.component_name}]: {json.dumps(messages[1], ensure_ascii=False)}"
|
||||
)
|
||||
except Exception:
|
||||
self.logger.warning(
|
||||
f"[{self.component_name}] Initial fail in selector loop: SP={str(messages[0])}, UP={str(messages[1])}, traceback: {format_exc()}."
|
||||
)
|
||||
|
||||
max_turn = int(
|
||||
self.config.get("runner", {})
|
||||
.get("selector_loop", {})
|
||||
.get("max_turn", 200)
|
||||
)
|
||||
temperature = (
|
||||
self.config.get("runner", {})
|
||||
.get("selector_loop", {})
|
||||
.get("temperature", 0.2)
|
||||
)
|
||||
|
||||
usage_stats = self._init_usage_stats()
|
||||
tool_stats = self._init_tools_stats()
|
||||
|
||||
total_turns = 0
|
||||
|
||||
chosen_index: int | None = None
|
||||
select_reason: str = ""
|
||||
for turn in range(max_turn):
|
||||
try:
|
||||
try:
|
||||
current_input_msg = messages[-1] if messages else None
|
||||
if current_input_msg is not None:
|
||||
self.logger.notice(
|
||||
f"[{self.component_name}] The {turn+1}th turn input: {json.dumps(current_input_msg, ensure_ascii=False)}"
|
||||
)
|
||||
except Exception:
|
||||
self.logger.warning(
|
||||
f"[{self.component_name}] {turn+1}th turn fail: {messages[-1] if messages else None}, traceback: {format_exc()}."
|
||||
)
|
||||
|
||||
self._debug_messages(turn, messages)
|
||||
|
||||
response = self.llm_manager.chat(
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
tool_choice="auto",
|
||||
temperature=temperature,
|
||||
)
|
||||
|
||||
first_tool_calls = None
|
||||
if hasattr(response, "choices") and response.choices:
|
||||
ch0 = response.choices[0]
|
||||
first_tool_calls = getattr(getattr(ch0, "message", None), "tool_calls", None)
|
||||
first_content = getattr(getattr(ch0, "message", None), "content", None) or ""
|
||||
else:
|
||||
first_content = ""
|
||||
|
||||
total_turns = turn + 1
|
||||
|
||||
self._response_log(response, first_content, first_tool_calls, turn + 1)
|
||||
|
||||
self._update_usage(response, usage_stats)
|
||||
|
||||
|
||||
if first_tool_calls:
|
||||
|
||||
if not self._make_assistant(first_content, first_tool_calls, messages):
|
||||
messages.append(
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请完成分析并调用 submit_result 工具给出最终选择与理由。",
|
||||
}
|
||||
)
|
||||
continue
|
||||
submit_found = False
|
||||
for tc in first_tool_calls:
|
||||
choice = self._extract_submit_choice(tc)
|
||||
if choice is not None:
|
||||
chosen_index = choice["index"]
|
||||
reason = choice.get("reason", "")
|
||||
self.logger.info(
|
||||
f"[{self.component_name}] choose: index={chosen_index}, reason={reason}"
|
||||
)
|
||||
select_reason = reason or ""
|
||||
submit_found = True
|
||||
|
||||
self._debug_last_message(turn, messages)
|
||||
break
|
||||
|
||||
|
||||
if not submit_found:
|
||||
|
||||
results = await self._submit_other_tool_calls(first_tool_calls)
|
||||
|
||||
self._make_tool_response(results, messages)
|
||||
|
||||
self._update_tool_call_statistic(results, tool_stats)
|
||||
|
||||
else:
|
||||
|
||||
messages.append(
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请完成分析并调用 submit_result 工具给出最终选择与理由。",
|
||||
}
|
||||
)
|
||||
|
||||
if chosen_index is not None:
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
f"[{self.component_name}] fail: {e}, traceback: {format_exc()}"
|
||||
)
|
||||
break
|
||||
|
||||
if chosen_index is None:
|
||||
# If the model provides no choice, fallback: pick the first successful one; otherwise the first
|
||||
for i, r in enumerate(candidates):
|
||||
try:
|
||||
if r.success:
|
||||
chosen_index = i
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if chosen_index is None:
|
||||
chosen_index = 0
|
||||
|
||||
if not (0 <= chosen_index < len(candidates)):
|
||||
chosen_index = 0
|
||||
|
||||
selected = candidates[chosen_index]
|
||||
|
||||
try:
|
||||
gp = selected.golden_patch[0] if selected.golden_patch else None
|
||||
if gp is None:
|
||||
patch_info = PatchInfo(patch_content="", test_status="", reasoning="")
|
||||
else:
|
||||
patch_info = PatchInfo(
|
||||
patch_content=gp.patch_content,
|
||||
test_status=gp.test_status,
|
||||
reasoning=gp.reasoning,
|
||||
)
|
||||
except Exception:
|
||||
patch_info = PatchInfo(patch_content="", test_status="", reasoning="")
|
||||
|
||||
selector_result = SelectorResult(
|
||||
instance_id=selected.instance_id,
|
||||
generator_id=selected.generator_id,
|
||||
image=selected.image,
|
||||
success=True,
|
||||
golden_patch=patch_info,
|
||||
llm_usage=usage_stats,
|
||||
tool_stats=tool_stats,
|
||||
total_turns=total_turns,
|
||||
select_reason=select_reason,
|
||||
error=None,
|
||||
)
|
||||
return selector_result
|
||||
|
||||
|
||||
254
src/managers/loop/types.py
Normal file
254
src/managers/loop/types.py
Normal file
@@ -0,0 +1,254 @@
|
||||
"""
|
||||
This module defines the GeneratorResult data structure for patch generation results.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Any, Optional
|
||||
from src.tools.base import (
|
||||
BASH_TOOL_NAME,
|
||||
STR_REPLACE_BASED_EDIT_TOOL_NAME,
|
||||
SEARCH_TOOL_NAME,
|
||||
SUBMIT_RESULT_TOOL_NAME,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMUsage:
|
||||
"""LLM usage statistics."""
|
||||
|
||||
prompt_tokens: int = 0
|
||||
completion_tokens: int = 0
|
||||
total_tokens: int = 0
|
||||
|
||||
def to_dict(self) -> Dict[str, int]:
|
||||
"""Serialize LLMUsage to a plain dictionary."""
|
||||
return {
|
||||
"prompt_tokens": int(self.prompt_tokens),
|
||||
"completion_tokens": int(self.completion_tokens),
|
||||
"total_tokens": int(self.total_tokens),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ToolStats:
|
||||
"""Tool usage statistics per tool.
|
||||
|
||||
Each tool is represented by a small map with two fields:
|
||||
- count: total invocation count
|
||||
- failed: failed invocation count
|
||||
"""
|
||||
|
||||
bash: Dict[str, int] = field(default_factory=lambda: {"count": 0, "failed": 0})
|
||||
edit: Dict[str, int] = field(default_factory=lambda: {"count": 0, "failed": 0})
|
||||
search: Dict[str, int] = field(default_factory=lambda: {"count": 0, "failed": 0})
|
||||
submit_result: Dict[str, int] = field(default_factory=lambda: {"count": 0, "failed": 0})
|
||||
|
||||
def to_dict(self) -> Dict[str, Dict[str, int]]:
|
||||
"""Serialize ToolStats to a plain dictionary."""
|
||||
return {
|
||||
BASH_TOOL_NAME: {"count": int(self.bash.get("count", 0)), "failed": int(self.bash.get("failed", 0))},
|
||||
STR_REPLACE_BASED_EDIT_TOOL_NAME: {"count": int(self.edit.get("count", 0)), "failed": int(self.edit.get("failed", 0))},
|
||||
SEARCH_TOOL_NAME: {"count": int(self.search.get("count", 0)), "failed": int(self.search.get("failed", 0))},
|
||||
SUBMIT_RESULT_TOOL_NAME: {"count": int(self.submit_result.get("count", 0)), "failed": int(self.submit_result.get("failed", 0))},
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class PatchInfo:
|
||||
"""Information about a generated patch."""
|
||||
|
||||
patch_content: str
|
||||
test_status: str
|
||||
reasoning: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class GeneratorResult:
|
||||
"""Result from a patch generator."""
|
||||
|
||||
instance_id: str
|
||||
generator_id: int
|
||||
image: str
|
||||
success: bool
|
||||
golden_patch: List[
|
||||
PatchInfo
|
||||
]
|
||||
llm_usage: LLMUsage
|
||||
tool_stats: ToolStats
|
||||
total_turns: int
|
||||
error: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "GeneratorResult":
|
||||
"""Create GeneratorResult from dictionary."""
|
||||
# Handle golden_patch conversion
|
||||
golden_patch = []
|
||||
if data.get("golden_patch"):
|
||||
for patch_data in data["golden_patch"]:
|
||||
if isinstance(patch_data, dict):
|
||||
golden_patch.append(
|
||||
PatchInfo(
|
||||
patch_content=patch_data.get("patch_content", ""),
|
||||
test_status=patch_data.get("test_status", ""),
|
||||
reasoning=patch_data.get("reasoning", ""),
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Legacy format: just patch content string
|
||||
golden_patch.append(
|
||||
PatchInfo(
|
||||
patch_content=str(patch_data), test_status="", reasoning=""
|
||||
)
|
||||
)
|
||||
|
||||
# Handle LLM usage
|
||||
llm_usage_data = data.get("llm_usage", {})
|
||||
llm_usage = LLMUsage(
|
||||
prompt_tokens=llm_usage_data.get("prompt_tokens", 0),
|
||||
completion_tokens=llm_usage_data.get("completion_tokens", 0),
|
||||
total_tokens=llm_usage_data.get("total_tokens", 0),
|
||||
)
|
||||
|
||||
# Handle tool stats
|
||||
tool_stats_data = data.get("tool_stats", {})
|
||||
tool_stats = ToolStats(
|
||||
bash=tool_stats_data.get(BASH_TOOL_NAME, 0),
|
||||
edit=tool_stats_data.get(STR_REPLACE_BASED_EDIT_TOOL_NAME, 0),
|
||||
search=tool_stats_data.get(SEARCH_TOOL_NAME, 0),
|
||||
submit_result=tool_stats_data.get(SUBMIT_RESULT_TOOL_NAME, 0),
|
||||
)
|
||||
|
||||
return cls(
|
||||
instance_id=data.get("instance_id", ""),
|
||||
generator_id=data.get("generator_id", 0),
|
||||
image=data.get("image", ""),
|
||||
success=data.get("success", False),
|
||||
golden_patch=golden_patch,
|
||||
llm_usage=llm_usage,
|
||||
tool_stats=tool_stats,
|
||||
total_turns=data.get("total_turns", 0),
|
||||
error=data.get("error"),
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert GeneratorResult to dictionary."""
|
||||
return {
|
||||
"instance_id": self.instance_id,
|
||||
"generator_id": self.generator_id,
|
||||
"image": self.image,
|
||||
"success": self.success,
|
||||
"golden_patch": [
|
||||
{
|
||||
"patch_content": patch.patch_content,
|
||||
"test_status": patch.test_status,
|
||||
"reasoning": patch.reasoning,
|
||||
}
|
||||
for patch in self.golden_patch
|
||||
],
|
||||
"llm_usage": {
|
||||
"prompt_tokens": self.llm_usage.prompt_tokens,
|
||||
"completion_tokens": self.llm_usage.completion_tokens,
|
||||
"total_tokens": self.llm_usage.total_tokens,
|
||||
},
|
||||
"tool_stats": {
|
||||
BASH_TOOL_NAME: self.tool_stats.bash,
|
||||
STR_REPLACE_BASED_EDIT_TOOL_NAME: self.tool_stats.edit,
|
||||
SEARCH_TOOL_NAME: self.tool_stats.search,
|
||||
SUBMIT_RESULT_TOOL_NAME: self.tool_stats.submit_result,
|
||||
},
|
||||
"total_turns": self.total_turns,
|
||||
"error": self.error,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class SelectorResult:
|
||||
"""Result from a patch selector.
|
||||
"""
|
||||
|
||||
instance_id: str
|
||||
generator_id: int
|
||||
image: str
|
||||
success: bool
|
||||
golden_patch: PatchInfo
|
||||
llm_usage: LLMUsage
|
||||
tool_stats: ToolStats
|
||||
total_turns: int
|
||||
select_reason: str
|
||||
error: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "SelectorResult":
|
||||
"""Create SelectorResult from dictionary."""
|
||||
|
||||
gp_data = data.get("golden_patch", {})
|
||||
if isinstance(gp_data, dict):
|
||||
golden_patch = PatchInfo(
|
||||
patch_content=gp_data.get("patch_content", ""),
|
||||
test_status=gp_data.get("test_status", ""),
|
||||
reasoning=gp_data.get("reasoning", ""),
|
||||
)
|
||||
else:
|
||||
golden_patch = PatchInfo(
|
||||
patch_content=str(gp_data) if gp_data is not None else "",
|
||||
test_status="",
|
||||
reasoning="",
|
||||
)
|
||||
|
||||
# LLM usage
|
||||
llm_usage_data = data.get("llm_usage", {})
|
||||
llm_usage = LLMUsage(
|
||||
prompt_tokens=llm_usage_data.get("prompt_tokens", 0),
|
||||
completion_tokens=llm_usage_data.get("completion_tokens", 0),
|
||||
total_tokens=llm_usage_data.get("total_tokens", 0),
|
||||
)
|
||||
|
||||
# Tool stats
|
||||
tool_stats_data = data.get("tool_stats", {})
|
||||
tool_stats = ToolStats(
|
||||
bash=tool_stats_data.get(BASH_TOOL_NAME, 0),
|
||||
edit=tool_stats_data.get(STR_REPLACE_BASED_EDIT_TOOL_NAME, 0),
|
||||
search=tool_stats_data.get(SEARCH_TOOL_NAME, 0),
|
||||
submit_result=tool_stats_data.get(SUBMIT_RESULT_TOOL_NAME, 0),
|
||||
)
|
||||
|
||||
return cls(
|
||||
instance_id=data.get("instance_id", ""),
|
||||
generator_id=data.get("generator_id", 0),
|
||||
image=data.get("image", ""),
|
||||
success=data.get("success", False),
|
||||
golden_patch=golden_patch,
|
||||
llm_usage=llm_usage,
|
||||
tool_stats=tool_stats,
|
||||
total_turns=data.get("total_turns", 0),
|
||||
select_reason=data.get("select_reason", ""),
|
||||
error=data.get("error"),
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert SelectorResult to dictionary."""
|
||||
return {
|
||||
"instance_id": self.instance_id,
|
||||
"generator_id": self.generator_id,
|
||||
"image": self.image,
|
||||
"success": self.success,
|
||||
"golden_patch": {
|
||||
"patch_content": self.golden_patch.patch_content,
|
||||
"test_status": self.golden_patch.test_status,
|
||||
"reasoning": self.golden_patch.reasoning,
|
||||
},
|
||||
"llm_usage": {
|
||||
"prompt_tokens": self.llm_usage.prompt_tokens,
|
||||
"completion_tokens": self.llm_usage.completion_tokens,
|
||||
"total_tokens": self.llm_usage.total_tokens,
|
||||
},
|
||||
"tool_stats": {
|
||||
BASH_TOOL_NAME: self.tool_stats.bash,
|
||||
STR_REPLACE_BASED_EDIT_TOOL_NAME: self.tool_stats.edit,
|
||||
SEARCH_TOOL_NAME: self.tool_stats.search,
|
||||
SUBMIT_RESULT_TOOL_NAME: self.tool_stats.submit_result,
|
||||
},
|
||||
"total_turns": self.total_turns,
|
||||
"select_reason": self.select_reason,
|
||||
"error": self.error,
|
||||
}
|
||||
Reference in New Issue
Block a user