refactor(dedupe): route through MultiProvider + cache wrapper + retry policy

``check_duplicate`` was calling ``litellm.completion(...)`` directly
via ``resolve_llm_config()``, bypassing every layer the main agent
loop runs through:

- :class:`MultiProvider` (so ``anthropic/...`` aliases never went
  through :class:`AnthropicCachingLitellmModel` and missed the
  ``cache_control`` patching on the system prompt — 4x cost on
  repeated dedupe calls within the same scan).
- :data:`DEFAULT_RETRY` (no retry on 429s / network blips — the
  caller's broad except-and-fallback was hiding this).

Switch to the SDK's :meth:`Model.get_response` directly: same model
selection, same retry policy, same cache wrapper. Extract assistant
text from ``ModelResponse.output`` via the canonical
``ResponseOutputMessage`` walk.

``check_duplicate`` is now async — drops the ``asyncio.to_thread``
indirection in ``_do_create``. Validation logic is fast-sync; running
it on the event loop is fine.

Drive-by: rename ``_DEFAULT_RETRY`` → ``DEFAULT_RETRY`` in
``run_config_factory`` so the dedupe path can reuse the same constant
without reaching into a private name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
0xallam
2026-04-25 15:25:44 -07:00
parent b3f7cfd040
commit 8a11f9dab5
3 changed files with 80 additions and 42 deletions
+3 -5
View File
@@ -2,7 +2,6 @@
from __future__ import annotations
import asyncio
import json
import logging
import re
@@ -152,7 +151,7 @@ _REQUIRED_FIELDS = {
}
def _do_create( # noqa: PLR0912
async def _do_create( # noqa: PLR0912
*,
title: str,
description: str,
@@ -238,7 +237,7 @@ def _do_create( # noqa: PLR0912
"endpoint": endpoint,
"method": method,
}
dedupe = check_duplicate(candidate, existing)
dedupe = await check_duplicate(candidate, existing)
if dedupe.get("is_duplicate"):
duplicate_id = dedupe.get("duplicate_id", "")
duplicate_title = next(
@@ -397,8 +396,7 @@ async def create_vulnerability_report(
``fix_before`` (verbatim source), ``fix_after`` (suggested
replacement).
"""
result = await asyncio.to_thread(
_do_create,
result = await _do_create(
title=title,
description=description,
impact=impact,