Private
Public Access
0
0

refactor(ai_client): migrate _deepseek_history call sites to provider_state.get_history('deepseek')

TIER-2 READ conductor/code_styleguides/error_handling.md before Phase 2 (deepseek migration; RLock re-entrance critical).

Phase 2 of code_path_audit_phase_3_provider_state_20260624. 11 sites in _send_deepseek (lines 2186-2414) migrated from _deepseek_history/_deepseek_history_lock to local capture history = provider_state.get_history('deepseek'). The RLock re-entrance is critical here — this was the deadlock-prone site that prompted cc7993e5. The local capture pattern uses one acquisition per function instead of one per call site, minimizing lock acquisitions while preserving the same RLock instance that _deepseek_history_lock aliased to.

4 with-blocks migrated (lines 2195, 2215, 2347, 2412). 6 _deepseek_history alias references migrated to history (lines 2196, 2197, 2201, 2216, 2354, 2414).

Verified: 30 tests pass across test_provider_state_migration (14) + test_deepseek_provider (7) + 5 ai_client test files. The test_lock_acquisition_no_deadlock regression test verifies RLock re-entrance works correctly inside the with history.lock: blocks.

Conventions: 1-space indentation, CRLF preserved, no comments added.
This commit is contained in:
2026-06-25 12:14:04 -04:00
parent 34a1e731c2
commit 79d0a56320
+11 -10
View File
@@ -2183,6 +2183,7 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
if not api_key: if not api_key:
if monitor.enabled: monitor.end_component("ai_client._send_deepseek") if monitor.enabled: monitor.end_component("ai_client._send_deepseek")
raise ValueError("DeepSeek API key not found in credentials.toml") raise ValueError("DeepSeek API key not found in credentials.toml")
history = provider_state.get_history("deepseek")
api_url = "https://api.deepseek.com/chat/completions" api_url = "https://api.deepseek.com/chat/completions"
headers = { headers = {
"Authorization": f"Bearer {api_key}", "Authorization": f"Bearer {api_key}",
@@ -2192,13 +2193,13 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
is_reasoner = _model in ("deepseek-reasoner", "deepseek-r1") is_reasoner = _model in ("deepseek-reasoner", "deepseek-r1")
# Update history following Anthropic pattern # Update history following Anthropic pattern
with _deepseek_history_lock: with history.lock:
_repair_deepseek_history(_deepseek_history) _repair_deepseek_history(history)
if discussion_history and not _deepseek_history: if discussion_history and not history:
user_content = f"[DISCUSSION HISTORY]\n\n{discussion_history}\n\n---\n\n{user_message}" user_content = f"[DISCUSSION HISTORY]\n\n{discussion_history}\n\n---\n\n{user_message}"
else: else:
user_content = user_message user_content = user_message
_deepseek_history.append({"role": "user", "content": user_content}) history.append({"role": "user", "content": user_content})
all_text_parts: list[str] = [] all_text_parts: list[str] = []
_cumulative_tool_bytes = 0 _cumulative_tool_bytes = 0
@@ -2212,8 +2213,8 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
sys_msg = {"role": "system", "content": f"{_get_combined_system_prompt()}\n\n<context>\n{md_content}\n</context>"} sys_msg = {"role": "system", "content": f"{_get_combined_system_prompt()}\n\n<context>\n{md_content}\n</context>"}
current_api_messages.append(sys_msg) current_api_messages.append(sys_msg)
with _deepseek_history_lock: with history.lock:
for i, msg in enumerate(_deepseek_history): for i, msg in enumerate(history):
# Create a clean copy of the message for the API # Create a clean copy of the message for the API
role = msg.get("role") role = msg.get("role")
api_msg = {"role": role} api_msg = {"role": role}
@@ -2344,14 +2345,14 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
thinking_tags = f"<thinking>\n{reasoning_content}\n</thinking>\n" thinking_tags = f"<thinking>\n{reasoning_content}\n</thinking>\n"
full_assistant_text = thinking_tags + assistant_text full_assistant_text = thinking_tags + assistant_text
with _deepseek_history_lock: with history.lock:
# DeepSeek/OpenAI: If tool_calls are present, content can be null but should usually be present # DeepSeek/OpenAI: If tool_calls are present, content can be null but should usually be present
msg_to_store: Metadata = {"role": "assistant", "content": assistant_text or None} msg_to_store: Metadata = {"role": "assistant", "content": assistant_text or None}
if reasoning_content: if reasoning_content:
msg_to_store["reasoning_content"] = reasoning_content msg_to_store["reasoning_content"] = reasoning_content
if tool_calls_raw: if tool_calls_raw:
msg_to_store["tool_calls"] = tool_calls_raw msg_to_store["tool_calls"] = tool_calls_raw
_deepseek_history.append(msg_to_store) history.append(msg_to_store)
if full_assistant_text: if full_assistant_text:
all_text_parts.append(full_assistant_text) all_text_parts.append(full_assistant_text)
@@ -2409,9 +2410,9 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
}) })
_append_comms("OUT", "request", {"message": f"[TOOL OUTPUT BUDGET EXCEEDED: {_cumulative_tool_bytes} bytes]"}) _append_comms("OUT", "request", {"message": f"[TOOL OUTPUT BUDGET EXCEEDED: {_cumulative_tool_bytes} bytes]"})
with _deepseek_history_lock: with history.lock:
for tr in tool_results_for_history: for tr in tool_results_for_history:
_deepseek_history.append(tr) history.append(tr)
res = "\n\n".join(all_text_parts) if all_text_parts else "(No text returned)" res = "\n\n".join(all_text_parts) if all_text_parts else "(No text returned)"
if monitor.enabled: monitor.end_component("ai_client._send_deepseek") if monitor.enabled: monitor.end_component("ai_client._send_deepseek")