Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion libs/openant-core/context/application_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
from anthropic import Anthropic
from dotenv import load_dotenv

# Ensure libs/openant-core is on sys.path so `utilities.*` imports resolve
# regardless of how this module is loaded.
_OPENANT_CORE_ROOT = str(Path(__file__).parent.parent)
if _OPENANT_CORE_ROOT not in sys.path:
sys.path.insert(0, _OPENANT_CORE_ROOT)

from utilities.model_config import MODEL_AUXILIARY # noqa: E402

# Load environment variables
load_dotenv()

Expand Down Expand Up @@ -462,7 +470,7 @@ def _build_type_descriptions() -> str:

def generate_application_context(
repo_path: Path,
model: str = "claude-sonnet-4-20250514",
model: str = MODEL_AUXILIARY,
force_regenerate: bool = False,
) -> ApplicationContext:
"""Generate application context using LLM analysis.
Expand Down
5 changes: 3 additions & 2 deletions libs/openant-core/context/generate_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))

from utilities.model_config import MODEL_AUXILIARY
from context.application_context import (
ApplicationType,
APPLICATION_TYPE_INFO,
Expand Down Expand Up @@ -78,8 +79,8 @@ def main():

parser.add_argument(
"--model", "-m",
default="claude-sonnet-4-20250514",
help="Anthropic model to use (default: claude-sonnet-4-20250514)",
default=MODEL_AUXILIARY,
help=f"Anthropic model to use (default: {MODEL_AUXILIARY})",
)

parser.add_argument(
Expand Down
3 changes: 2 additions & 1 deletion libs/openant-core/core/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,8 @@ def run_analysis(
checkpoint.dir = checkpoint_path

# Select model
model_id = "claude-opus-4-6" if model == "opus" else "claude-sonnet-4-20250514"
from utilities.model_config import MODEL_AUXILIARY, MODEL_PRIMARY
model_id = MODEL_PRIMARY if model == "opus" else MODEL_AUXILIARY
print(f"[Analyze] Model: {model_id}", file=sys.stderr)

# Initialize client
Expand Down
3 changes: 2 additions & 1 deletion libs/openant-core/core/enhancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ def enhance_dataset(
# Configure global rate limiter
configure_rate_limiter(backoff_seconds=float(backoff_seconds))

model_id = "claude-sonnet-4-20250514" if model == "sonnet" else "claude-opus-4-6"
from utilities.model_config import MODEL_AUXILIARY, MODEL_PRIMARY
model_id = MODEL_AUXILIARY if model == "sonnet" else MODEL_PRIMARY
print(f"[Enhance] Mode: {mode}", file=sys.stderr)
print(f"[Enhance] Model: {model_id}", file=sys.stderr)

Expand Down
3 changes: 2 additions & 1 deletion libs/openant-core/core/reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,11 +587,12 @@ def _record_usage_in_tracker(usage: dict):
"""Record usage in the global TokenTracker so step_context captures it."""
try:
from utilities.llm_client import get_global_tracker
from utilities.model_config import MODEL_PRIMARY
tracker = get_global_tracker()
# Record as a single aggregated call
if usage.get("total_tokens", 0) > 0:
tracker.record_call(
model="claude-opus-4-6",
model=MODEL_PRIMARY,
input_tokens=usage["input_tokens"],
output_tokens=usage["output_tokens"],
)
Expand Down
3 changes: 2 additions & 1 deletion libs/openant-core/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,8 @@ def run_experiment(
Experiment results with metrics
"""
# Select model
model_id = "claude-opus-4-20250514" if model == "opus" else "claude-sonnet-4-20250514"
from utilities.model_config import MODEL_AUXILIARY, MODEL_PRIMARY
model_id = MODEL_PRIMARY if model == "opus" else MODEL_AUXILIARY
print(f"Using model: {model_id}")
print(f"Enhanced context: {enhanced}")
print(f"Context correction: {correct_context}")
Expand Down
12 changes: 11 additions & 1 deletion libs/openant-core/generate_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,26 @@
import json
import html
import os
import sys
from datetime import datetime
from pathlib import Path

import anthropic
from dotenv import load_dotenv

# Ensure libs/openant-core is on sys.path so `utilities.*` imports resolve
# regardless of the caller's working directory.
_OPENANT_CORE_ROOT = str(Path(__file__).parent)
if _OPENANT_CORE_ROOT not in sys.path:
sys.path.insert(0, _OPENANT_CORE_ROOT)

from utilities.model_config import MODEL_AUXILIARY # noqa: E402

# Load environment variables from .env file
load_dotenv()


REPORT_MODEL = "claude-sonnet-4-20250514"
REPORT_MODEL = MODEL_AUXILIARY
MAX_TOKENS = 4096


Expand Down
5 changes: 3 additions & 2 deletions libs/openant-core/openant/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,9 +810,10 @@ def cmd_report_data(args):
{findings_text}
"""
print("[Report] Generating remediation guidance (LLM)...", file=sys.stderr)
from utilities.model_config import MODEL_AUXILIARY
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-20250514",
model=MODEL_AUXILIARY,
max_tokens=4096,
messages=[{"role": "user", "content": prompt}],
)
Expand All @@ -829,7 +830,7 @@ def _linkify_finding(m):
usage = response.usage
tracker = get_global_tracker()
tracker.record_call(
model="claude-sonnet-4-20250514",
model=MODEL_AUXILIARY,
input_tokens=usage.input_tokens,
output_tokens=usage.output_tokens,
)
Expand Down
17 changes: 12 additions & 5 deletions libs/openant-core/report/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,25 @@
from pathlib import Path
from dotenv import load_dotenv

# Ensure libs/openant-core is on sys.path so `utilities.*` imports resolve.
_OPENANT_CORE_ROOT = str(Path(__file__).parent.parent)
if _OPENANT_CORE_ROOT not in sys.path:
sys.path.insert(0, _OPENANT_CORE_ROOT)

from utilities.model_config import MODEL_PRIMARY, MODEL_AUXILIARY # noqa: E402

from .schema import validate_pipeline_output, ValidationError

load_dotenv()

PROMPTS_DIR = Path(__file__).parent / "prompts"
MODEL = "claude-opus-4-6"
MODEL = MODEL_PRIMARY

# Pricing per million tokens
# Pricing per million tokens. Aliased opus keys retained for backward
# compatibility with any cached/legacy responses that recorded the literal.
_PRICING = {
"claude-opus-4-6": {"input": 15.00, "output": 75.00},
"claude-opus-4-20250514": {"input": 15.00, "output": 75.00},
"claude-sonnet-4-20250514": {"input": 3.00, "output": 15.00},
MODEL_PRIMARY: {"input": 15.00, "output": 75.00},
MODEL_AUXILIARY: {"input": 3.00, "output": 15.00},
}
_DEFAULT_PRICING = {"input": 3.00, "output": 15.00}

Expand Down
93 changes: 93 additions & 0 deletions libs/openant-core/tests/test_model_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Tests for the central model configuration module."""
import re
from pathlib import Path

import pytest

from utilities import model_config
from utilities.model_config import MODEL_AUXILIARY, MODEL_DEFAULT, MODEL_PRIMARY


# Regex for a valid Claude model identifier (e.g. claude-opus-4-20250514,
# claude-sonnet-4-6, claude-haiku-4-5).
_MODEL_ID_RE = re.compile(r"^claude-(opus|sonnet|haiku)-[0-9A-Za-z-]+$")

# Regex used by the regression test to detect any hardcoded
# claude-opus-* / claude-sonnet-* string literal.
_HARDCODED_LITERAL_RE = re.compile(r"claude-(?:opus|sonnet)-[0-9][0-9A-Za-z-]*")


class TestModelConstants:
"""Constants must exist, be non-empty strings, and match Claude model id format."""

def test_model_primary_is_valid_string(self):
assert isinstance(MODEL_PRIMARY, str)
assert MODEL_PRIMARY, "MODEL_PRIMARY must be non-empty"
assert _MODEL_ID_RE.match(MODEL_PRIMARY), (
f"MODEL_PRIMARY={MODEL_PRIMARY!r} does not match expected "
f"claude-(opus|sonnet|haiku)-... format"
)

def test_model_auxiliary_is_valid_string(self):
assert isinstance(MODEL_AUXILIARY, str)
assert MODEL_AUXILIARY, "MODEL_AUXILIARY must be non-empty"
assert _MODEL_ID_RE.match(MODEL_AUXILIARY), (
f"MODEL_AUXILIARY={MODEL_AUXILIARY!r} does not match expected "
f"claude-(opus|sonnet|haiku)-... format"
)

def test_model_default_is_valid_string(self):
assert isinstance(MODEL_DEFAULT, str)
assert MODEL_DEFAULT, "MODEL_DEFAULT must be non-empty"
assert _MODEL_ID_RE.match(MODEL_DEFAULT)

def test_module_exposes_all_three_constants(self):
for name in ("MODEL_PRIMARY", "MODEL_AUXILIARY", "MODEL_DEFAULT"):
assert hasattr(model_config, name), f"model_config missing {name}"


class TestNoHardcodedModelLiterals:
"""Regression test: no hardcoded claude-opus-*/claude-sonnet-* literals
may reappear in libs/openant-core/*.py outside of model_config.py.

If this test fails, replace the offending literal with an import of
MODEL_PRIMARY / MODEL_AUXILIARY / MODEL_DEFAULT from utilities.model_config.
"""

# Path to libs/openant-core (this file is at libs/openant-core/tests/...)
_CORE_ROOT = Path(__file__).parent.parent

# Files exempt from the scan (the constants live here, by design)
_EXEMPT = {
_CORE_ROOT / "utilities" / "model_config.py",
# The regression test itself contains the regex pattern source.
Path(__file__).resolve(),
}

def test_no_hardcoded_model_strings_outside_model_config(self):
offenders: list[tuple[Path, int, str]] = []

for py_path in self._CORE_ROOT.rglob("*.py"):
resolved = py_path.resolve()
if resolved in {p.resolve() for p in self._EXEMPT}:
continue

try:
text = py_path.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError):
continue

for lineno, line in enumerate(text.splitlines(), start=1):
if _HARDCODED_LITERAL_RE.search(line):
offenders.append((py_path, lineno, line.strip()))

if offenders:
details = "\n".join(
f" {path.relative_to(self._CORE_ROOT)}:{lineno}: {snippet}"
for path, lineno, snippet in offenders
)
pytest.fail(
"Found hardcoded claude-opus-*/claude-sonnet-* literals outside "
"utilities/model_config.py. Replace them with imports from "
"utilities.model_config:\n" + details
)
17 changes: 9 additions & 8 deletions libs/openant-core/tests/test_token_tracker.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tests for TokenTracker."""
from utilities.llm_client import TokenTracker, MODEL_PRICING
from utilities.model_config import MODEL_PRIMARY, MODEL_AUXILIARY


class TestTokenTracker:
Expand All @@ -13,9 +14,9 @@ def test_initial_state(self):

def test_record_call_known_model(self):
tracker = TokenTracker()
result = tracker.record_call("claude-sonnet-4-20250514", 1000, 500)
result = tracker.record_call(MODEL_AUXILIARY, 1000, 500)

assert result["model"] == "claude-sonnet-4-20250514"
assert result["model"] == MODEL_AUXILIARY
assert result["input_tokens"] == 1000
assert result["output_tokens"] == 500
# Sonnet: $3/M input, $15/M output
Expand All @@ -31,8 +32,8 @@ def test_record_call_unknown_model_uses_default(self):

def test_cumulative_tracking(self):
tracker = TokenTracker()
tracker.record_call("claude-sonnet-4-20250514", 1000, 500)
tracker.record_call("claude-sonnet-4-20250514", 2000, 1000)
tracker.record_call(MODEL_AUXILIARY, 1000, 500)
tracker.record_call(MODEL_AUXILIARY, 2000, 1000)

assert tracker.total_input_tokens == 3000
assert tracker.total_output_tokens == 1500
Expand All @@ -41,7 +42,7 @@ def test_cumulative_tracking(self):

def test_reset(self):
tracker = TokenTracker()
tracker.record_call("claude-sonnet-4-20250514", 1000, 500)
tracker.record_call(MODEL_AUXILIARY, 1000, 500)
tracker.reset()

assert tracker.total_input_tokens == 0
Expand All @@ -51,7 +52,7 @@ def test_reset(self):

def test_get_summary_includes_calls(self):
tracker = TokenTracker()
tracker.record_call("claude-sonnet-4-20250514", 100, 50)
tracker.record_call(MODEL_AUXILIARY, 100, 50)
summary = tracker.get_summary()

assert summary["total_calls"] == 1
Expand All @@ -60,14 +61,14 @@ def test_get_summary_includes_calls(self):

def test_get_totals_excludes_calls(self):
tracker = TokenTracker()
tracker.record_call("claude-sonnet-4-20250514", 100, 50)
tracker.record_call(MODEL_AUXILIARY, 100, 50)
totals = tracker.get_totals()

assert totals["total_calls"] == 1
assert "calls" not in totals

def test_opus_pricing(self):
tracker = TokenTracker()
result = tracker.record_call("claude-opus-4-20250514", 1_000_000, 1_000_000)
result = tracker.record_call(MODEL_PRIMARY, 1_000_000, 1_000_000)
# Opus: $15/M input, $75/M output
assert result["cost_usd"] == 90.0
3 changes: 2 additions & 1 deletion libs/openant-core/utilities/agentic_enhancer/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import anthropic

from ..llm_client import TokenTracker, get_global_tracker
from ..model_config import MODEL_AUXILIARY
from ..rate_limiter import get_rate_limiter
from .repository_index import RepositoryIndex
from .tools import TOOL_DEFINITIONS, ToolExecutor
Expand All @@ -26,7 +27,7 @@


# Use Sonnet for exploration (cost-effective)
AGENT_MODEL = "claude-sonnet-4-20250514"
AGENT_MODEL = MODEL_AUXILIARY

# Safety limits
MAX_ITERATIONS = 20
Expand Down
5 changes: 3 additions & 2 deletions libs/openant-core/utilities/context_corrector.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from typing import Optional

from .llm_client import AnthropicClient, TokenTracker, get_global_tracker
from .model_config import MODEL_AUXILIARY


# Maximum characters per batch (leaving room for prompt overhead)
Expand Down Expand Up @@ -102,7 +103,7 @@ def parse_missing_context_with_llm(
prompt = get_missing_context_prompt(reasoning)

try:
llm_response = client.analyze_sync(prompt, model="claude-sonnet-4-20250514")
llm_response = client.analyze_sync(prompt, model=MODEL_AUXILIARY)
parsed = _parse_json_response(llm_response)

if parsed and "missing_context" in parsed:
Expand Down Expand Up @@ -254,7 +255,7 @@ def search_files_for_context(
prompt = get_file_search_prompt(missing_context, files_content, batch_info)

try:
response = client.analyze_sync(prompt, model="claude-sonnet-4-20250514")
response = client.analyze_sync(prompt, model=MODEL_AUXILIARY)
result = _parse_json_response(response)

if result and result.get("found_files"):
Expand Down
5 changes: 3 additions & 2 deletions libs/openant-core/utilities/context_enhancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

from .llm_client import AnthropicClient, TokenTracker, get_global_tracker, reset_global_tracker
from .agentic_enhancer import RepositoryIndex, enhance_unit_with_agent, load_index_from_file
from .model_config import MODEL_AUXILIARY
from .rate_limiter import get_rate_limiter, is_rate_limit_error, is_retryable_error

# Avoid circular import — import checkpoint at usage site
Expand All @@ -45,7 +46,7 @@ def _get_step_checkpoint():


# Use Sonnet for context enhancement (cost-effective auxiliary task)
CONTEXT_ENHANCEMENT_MODEL = "claude-sonnet-4-20250514"
CONTEXT_ENHANCEMENT_MODEL = MODEL_AUXILIARY


def _build_error_info(exc: Exception) -> dict:
Expand Down Expand Up @@ -568,7 +569,7 @@ def enhance_dataset_agentic(
remaining = total - len(processed_ids)
self._log("info", f"Enhancing {remaining} units with agentic analysis ({len(processed_ids)} already done)", units=remaining)
self._log("info", "Mode: Iterative tool use (traces call paths)")
self._log("info", "Model: claude-sonnet-4-20250514")
self._log("info", f"Model: {CONTEXT_ENHANCEMENT_MODEL}")
mode = "sequential" if workers <= 1 else f"parallel ({workers} workers)"
self._log("info", f"Workers: {mode}")
if checkpoint_dir:
Expand Down
Loading
Loading