WIP: Regression hell
This commit is contained in:
137
src/ai_client.py
137
src/ai_client.py
@@ -252,7 +252,12 @@ def _classify_gemini_error(exc: Exception) -> ProviderError:
|
||||
return ProviderError("unknown", "gemini", exc)
|
||||
|
||||
def _classify_deepseek_error(exc: Exception) -> ProviderError:
|
||||
body = str(exc).lower()
|
||||
body = ""
|
||||
if isinstance(exc, requests.exceptions.HTTPError) and exc.response is not None:
|
||||
body = exc.response.text.lower()
|
||||
else:
|
||||
body = str(exc).lower()
|
||||
|
||||
if "429" in body or "rate" in body:
|
||||
return ProviderError("rate_limit", "deepseek", exc)
|
||||
if "401" in body or "403" in body or "auth" in body or "api key" in body:
|
||||
@@ -263,6 +268,13 @@ def _classify_deepseek_error(exc: Exception) -> ProviderError:
|
||||
return ProviderError("quota", "deepseek", exc)
|
||||
if "connection" in body or "timeout" in body or "network" in body:
|
||||
return ProviderError("network", "deepseek", exc)
|
||||
|
||||
if "400" in body or "bad request" in body:
|
||||
# Try to wrap the original error with the response body for better debugging
|
||||
if body:
|
||||
new_exc = Exception(f"Bad Request (400): {body}")
|
||||
return ProviderError("unknown", "deepseek", new_exc)
|
||||
|
||||
return ProviderError("unknown", "deepseek", exc)
|
||||
|
||||
def set_provider(provider: str, model: str) -> None:
|
||||
@@ -637,6 +649,54 @@ def _build_file_diff_text(changed_items: list[dict[str, Any]]) -> str:
|
||||
parts.append(f"### `{path}` (no changes detected)")
|
||||
return "\n\n---\n\n".join(parts)
|
||||
|
||||
def _build_deepseek_tools() -> list[dict[str, Any]]:
|
||||
mcp_tools: list[dict[str, Any]] = []
|
||||
for spec in mcp_client.MCP_TOOL_SPECS:
|
||||
if _agent_tools.get(spec["name"], True):
|
||||
mcp_tools.append({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": spec["name"],
|
||||
"description": spec["description"],
|
||||
"parameters": spec["parameters"],
|
||||
}
|
||||
})
|
||||
tools_list = mcp_tools
|
||||
if _agent_tools.get(TOOL_NAME, True):
|
||||
powershell_tool: dict[str, Any] = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": TOOL_NAME,
|
||||
"description": (
|
||||
"Run a PowerShell script within the project base_dir. "
|
||||
"Use this to create, edit, rename, or delete files and directories. "
|
||||
"The working directory is set to base_dir automatically. "
|
||||
"Always prefer targeted edits over full rewrites where possible. "
|
||||
"stdout and stderr are returned to you as the result."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"script": {
|
||||
"type": "string",
|
||||
"description": "The PowerShell script to execute."
|
||||
}
|
||||
},
|
||||
"required": ["script"]
|
||||
}
|
||||
}
|
||||
}
|
||||
tools_list.append(powershell_tool)
|
||||
return tools_list
|
||||
|
||||
_CACHED_DEEPSEEK_TOOLS: Optional[list[dict[str, Any]]] = None
|
||||
|
||||
def _get_deepseek_tools() -> list[dict[str, Any]]:
|
||||
global _CACHED_DEEPSEEK_TOOLS
|
||||
if _CACHED_DEEPSEEK_TOOLS is None:
|
||||
_CACHED_DEEPSEEK_TOOLS = _build_deepseek_tools()
|
||||
return _CACHED_DEEPSEEK_TOOLS
|
||||
|
||||
def _content_block_to_dict(block: Any) -> dict[str, Any]:
|
||||
if isinstance(block, dict):
|
||||
return block
|
||||
@@ -1356,33 +1416,56 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
current_api_messages: list[dict[str, Any]] = []
|
||||
|
||||
is_reasoner = _model == "deepseek-reasoner"
|
||||
|
||||
# Update history following Anthropic pattern
|
||||
with _deepseek_history_lock:
|
||||
for msg in _deepseek_history:
|
||||
current_api_messages.append(msg)
|
||||
initial_user_message_content = user_message
|
||||
if discussion_history:
|
||||
initial_user_message_content = f"[DISCUSSION HISTORY]\n\n{discussion_history}\n\n---\n\n{user_message}"
|
||||
current_api_messages.append({"role": "user", "content": initial_user_message_content})
|
||||
request_payload: dict[str, Any] = {
|
||||
"model": _model,
|
||||
"messages": current_api_messages,
|
||||
"temperature": _temperature,
|
||||
"max_tokens": _max_tokens,
|
||||
"stream": stream,
|
||||
}
|
||||
sys_msg = {"role": "system", "content": f"{_get_combined_system_prompt()}\n\n<context>\n{md_content}\n</context>"}
|
||||
request_payload["messages"].insert(0, sys_msg)
|
||||
if discussion_history and not _deepseek_history:
|
||||
user_content = f"[DISCUSSION HISTORY]\n\n{discussion_history}\n\n---\n\n{user_message}"
|
||||
else:
|
||||
user_content = user_message
|
||||
_deepseek_history.append({"role": "user", "content": user_content})
|
||||
|
||||
all_text_parts: list[str] = []
|
||||
_cumulative_tool_bytes = 0
|
||||
round_idx = 0
|
||||
while round_idx <= MAX_TOOL_ROUNDS + 1:
|
||||
|
||||
for round_idx in range(MAX_TOOL_ROUNDS + 2):
|
||||
current_api_messages: list[dict[str, Any]] = []
|
||||
with _deepseek_history_lock:
|
||||
for msg in _deepseek_history:
|
||||
current_api_messages.append(msg)
|
||||
|
||||
sys_msg = {"role": "system", "content": f"{_get_combined_system_prompt()}\n\n<context>\n{md_content}\n</context>"}
|
||||
current_api_messages.insert(0, sys_msg)
|
||||
|
||||
request_payload: dict[str, Any] = {
|
||||
"model": _model,
|
||||
"messages": current_api_messages,
|
||||
"stream": stream,
|
||||
}
|
||||
|
||||
if not is_reasoner:
|
||||
request_payload["temperature"] = _temperature
|
||||
request_payload["max_tokens"] = _max_tokens
|
||||
tools = _get_deepseek_tools()
|
||||
if tools:
|
||||
request_payload["tools"] = tools
|
||||
|
||||
events.emit("request_start", payload={"provider": "deepseek", "model": _model, "round": round_idx, "streaming": stream})
|
||||
|
||||
try:
|
||||
response = requests.post(api_url, headers=headers, json=request_payload, timeout=60, stream=stream)
|
||||
response = requests.post(api_url, headers=headers, json=request_payload, timeout=120, stream=stream)
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise _classify_deepseek_error(e) from e
|
||||
|
||||
assistant_text = ""
|
||||
tool_calls_raw = []
|
||||
reasoning_content = ""
|
||||
finish_reason = "stop"
|
||||
usage = {}
|
||||
|
||||
if stream:
|
||||
aggregated_content = ""
|
||||
aggregated_tool_calls: list[dict[str, Any]] = []
|
||||
@@ -1443,10 +1526,12 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
|
||||
reasoning_content = message.get("reasoning_content", "")
|
||||
finish_reason = choice.get("finish_reason", "stop")
|
||||
usage = response_data.get("usage", {})
|
||||
|
||||
thinking_tags = ""
|
||||
if reasoning_content:
|
||||
thinking_tags = f"<thinking>\n{reasoning_content}\n</thinking>\n"
|
||||
full_assistant_text = thinking_tags + assistant_text
|
||||
|
||||
with _deepseek_history_lock:
|
||||
msg_to_store: dict[str, Any] = {"role": "assistant", "content": assistant_text}
|
||||
if reasoning_content:
|
||||
@@ -1454,8 +1539,10 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
|
||||
if tool_calls_raw:
|
||||
msg_to_store["tool_calls"] = tool_calls_raw
|
||||
_deepseek_history.append(msg_to_store)
|
||||
|
||||
if full_assistant_text:
|
||||
all_text_parts.append(full_assistant_text)
|
||||
|
||||
_append_comms("IN", "response", {
|
||||
"round": round_idx,
|
||||
"stop_reason": finish_reason,
|
||||
@@ -1464,6 +1551,7 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
|
||||
"usage": usage,
|
||||
"streaming": stream
|
||||
})
|
||||
|
||||
if finish_reason != "tool_calls" and not tool_calls_raw:
|
||||
break
|
||||
if round_idx > MAX_TOOL_ROUNDS:
|
||||
@@ -1507,16 +1595,11 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
|
||||
"content": f"SYSTEM WARNING: Cumulative tool output exceeded {_MAX_TOOL_OUTPUT_BYTES // 1000}KB budget. Provide your final answer now."
|
||||
})
|
||||
_append_comms("OUT", "request", {"message": f"[TOOL OUTPUT BUDGET EXCEEDED: {_cumulative_tool_bytes} bytes]"})
|
||||
|
||||
with _deepseek_history_lock:
|
||||
for tr in tool_results_for_history:
|
||||
_deepseek_history.append(tr)
|
||||
next_messages: list[dict[str, Any]] = []
|
||||
with _deepseek_history_lock:
|
||||
for msg in _deepseek_history:
|
||||
next_messages.append(msg)
|
||||
next_messages.insert(0, sys_msg)
|
||||
request_payload["messages"] = next_messages
|
||||
round_idx += 1
|
||||
|
||||
return "\n\n".join(all_text_parts) if all_text_parts else "(No text returned)"
|
||||
except Exception as e:
|
||||
raise _classify_deepseek_error(e) from e
|
||||
|
||||
Reference in New Issue
Block a user