Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,6 @@ async def analyze_code(
],
"temperature": 0.3, # Lower temperature for more deterministic responses
"max_tokens": 2000,
# ✅ Validated and enforced JSON output per provider
"response_format": {"type": "json"},
}
if provider_name:
request["provider"] = provider_name
Expand All @@ -149,7 +147,7 @@ async def analyze_code(
file_path=file_path,
provider=provider_name or "default",
)
response = self.llm_manager.complete(request)
response = await asyncio.to_thread(self.llm_manager.complete, request)

# LLMResponse is a dataclass and so always truthy; a failed call carries
# `error` with empty content. Checking truthiness alone would send "" on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
"""

import time
from typing import Any
from typing import TYPE_CHECKING, Any

import structlog

from codeflow_engine.actions.quality_engine.models import ToolResult


if TYPE_CHECKING:
from codeflow_engine.actions.llm.manager import ActionLLMProviderManager

logger = structlog.get_logger(__name__)


Expand Down Expand Up @@ -206,7 +210,7 @@ async def run_ai_analysis(
return None


async def initialize_llm_manager() -> Any | None:
async def initialize_llm_manager() -> "ActionLLMProviderManager | None":
"""Initialize the LLM manager for AI analysis.

Delegates to the quality engine's single initializer rather than building a
Expand Down
12 changes: 8 additions & 4 deletions engine/codeflow_engine/actions/quality_engine/ai/ai_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
integrating with the CodeFlow LLM provider system.
"""

import json
import asyncio
from enum import StrEnum
import json
from pathlib import Path
from typing import Any

Expand All @@ -18,6 +19,7 @@
from codeflow_engine.actions.quality_engine.models import ToolResult
from codeflow_engine.agents.models import CodeIssue


logger = structlog.get_logger(__name__)

# System prompt templates
Expand Down Expand Up @@ -167,17 +169,19 @@ async def run_ai_analysis(
{"role": "user", "content": analysis_prompt},
],
"temperature": 0.1,
"response_format": {"type": "json_object"},
}
if provider_name:
request["provider"] = provider_name
if model:
request["model"] = model

response = llm_manager.complete(request)
response = await asyncio.to_thread(llm_manager.complete, request)

if not response or not response.content:
logger.warning("No response received from LLM")
logger.warning(
"No response received from LLM",
error=getattr(response, "error", None),
)
return None

# Report what was actually used, not what was asked for — when either was
Expand Down
16 changes: 9 additions & 7 deletions engine/tests/test_sluice_request_metadata.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Guards the Sluice request-metadata contract (ADR 10, raised to MUST by ADR 17).

These tests assert the *contract* — what the gateway requires — not what the code
These tests assert the *contract* what the gateway requires not what the code
currently happens to send. That distinction is the reason this file exists.

The same contract was silently violated for months in house-of-veritas: the call
Expand All @@ -12,7 +12,7 @@
So: every assertion below is written against
`phoenixvc/sluice` docs/architecture/17-mandatory-request-metadata.md. If an
assertion here ever disagrees with the implementation, the implementation is what
moves — unless the ADR itself changed, in which case update the citation too.
moves unless the ADR itself changed, in which case update the citation too.

codeflow-engine's virtual key carries no `use:` in the gateway's keys.yaml, which
classifies it as a *service*: enforced, not exempt.
Expand Down Expand Up @@ -40,7 +40,7 @@
from codeflow_engine.core.llm.sluice_provider import SluiceProvider

# ADR 10 naming rule, unchanged by ADR 17: `app` and `agent` become Prometheus label
# values, so they must be lowercase kebab-case — stable, one token per dimension.
# values, so they must be lowercase kebab-case stable, one token per dimension.
KEBAB_CASE = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")

SLUICE_BASE_URL = "https://litellm.sluice.example"
Expand Down Expand Up @@ -68,7 +68,7 @@ def recorder(monkeypatch: pytest.MonkeyPatch) -> _CompletionsRecorder:
"""Install a fake `openai` module so tests assert on the request, not the network.

Stubbing at the module boundary rather than patching the provider's `client`
attribute keeps `_initialize_client` in the code path — that is where the
attribute keeps `_initialize_client` in the code path that is where the
base_url and api_key actually get bound.
"""
completions = _CompletionsRecorder()
Expand Down Expand Up @@ -144,7 +144,7 @@ def test_does_not_send_superseded_field_names(


class TestNamingRules:
"""ADR 10 naming rules — violations are counted, not rejected, so nothing else catches them."""
"""ADR 10 naming rules violations are counted, not rejected, so nothing else catches them."""

def test_prometheus_labelled_fields_are_kebab_case(
self, recorder: _CompletionsRecorder, sluice_env: None
Expand Down Expand Up @@ -199,7 +199,7 @@ def test_known_literal_strings_still_resolve(self) -> None:


class TestUntaggedTrafficIsRefused:
"""An untagged request is accepted by the gateway today — local failure is the only signal."""
"""An untagged request is accepted by the gateway today local failure is the only signal."""

def test_sluice_route_without_an_agent_refuses_to_send(
self, recorder: _CompletionsRecorder, sluice_env: None
Expand Down Expand Up @@ -300,7 +300,7 @@ def test_guard_ignores_path_and_trailing_slash_differences(
) -> None:
from codeflow_engine.actions.llm.providers import OpenAIProvider

# Same host, cosmetically different URL — must not slip past the guard.
# Same host, cosmetically different URL must not slip past the guard.
with pytest.raises(SluiceMetadataError):
OpenAIProvider(
{"api_key": "sk-test", "base_url": f"{SLUICE_BASE_URL}/v1/"}
Expand Down Expand Up @@ -848,6 +848,7 @@ async def test_analysis_requests_are_tagged(
"app": "codeflow-engine",
"agent": "quality-analyzer",
}
assert "response_format" not in recorder.calls[0]

@pytest.mark.asyncio
async def test_does_not_ask_the_gateway_for_a_vendor_model(
Expand Down Expand Up @@ -970,3 +971,4 @@ async def test_the_code_analyzer_shares_the_same_tag(
await AICodeAnalyzer(manager).analyze_code("sample.py", "x = 1\n")

assert sent_metadata(recorder)["agent"] == "quality-analyzer"
assert "response_format" not in recorder.calls[0]
Loading