selcuksntrk's picture
Add IaC-SecFix agent package
0953e56 verified
Raw
History Blame Contribute Delete
5.46 kB
from __future__ import annotations
import inspect
import json
from dataclasses import dataclass
from typing import Any
from pydantic import ValidationError
from .config import RuntimeConfig, vllm_extra_body
from .schemas import FixCandidate
def extract_json_object(text: str) -> str | None:
start = text.find("{")
if start == -1:
return None
depth = 0
in_string = False
escaped = False
for index in range(start, len(text)):
char = text[index]
if in_string:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == '"':
in_string = False
continue
if char == '"':
in_string = True
elif char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return text[start : index + 1]
return None
def coerce_fix_candidate(raw: dict[str, Any]) -> dict[str, Any]:
patch = raw.get("patch") or raw.get("diff") or ""
fixed_file = raw.get("fixed_file") or raw.get("content") or raw.get("file_content")
resolved = raw.get("resolved_policy_ids") or raw.get("resolved") or []
if isinstance(resolved, str):
resolved = [resolved]
return {
"patch": patch,
"fixed_file": fixed_file,
"resolved_policy_ids": resolved,
"explanation": raw.get("explanation") or raw.get("summary") or "",
"verification_commands": raw.get("verification_commands") or [],
"risk_notes": raw.get("risk_notes") or [],
"requires_human_approval": bool(raw.get("requires_human_approval", True)),
}
def parse_fix_candidate(text: str) -> tuple[FixCandidate | None, bool]:
json_text = extract_json_object(text)
if json_text is None:
return None, False
try:
return FixCandidate.model_validate_json(json_text), True
except ValidationError:
try:
raw = json.loads(json_text)
if not isinstance(raw, dict):
return None, False
return FixCandidate.model_validate(coerce_fix_candidate(raw)), True
except Exception:
return None, False
@dataclass(slots=True)
class ChatResult:
text: str
used_json_mode: bool
class OpenAICompatibleLLM:
"""Thin OpenAI-compatible client used by the PatchAgent."""
def __init__(self, config: RuntimeConfig):
from openai import OpenAI
self.config = config
self.client = OpenAI(base_url=config.base_url, api_key=config.api_key)
def chat(self, messages: list[dict[str, str]], json_mode: bool = True) -> ChatResult:
kwargs: dict[str, Any] = {
"model": self.config.model,
"messages": messages,
"temperature": self.config.temperature,
"top_p": self.config.top_p,
"max_tokens": self.config.max_tokens,
"extra_body": vllm_extra_body(),
}
if json_mode:
kwargs["response_format"] = {"type": "json_object"}
try:
completion = self.client.chat.completions.create(**kwargs)
return ChatResult(completion.choices[0].message.content or "{}", used_json_mode=json_mode)
except Exception:
kwargs.pop("response_format", None)
kwargs.pop("extra_body", None)
completion = self.client.chat.completions.create(**kwargs)
return ChatResult(completion.choices[0].message.content or "{}", used_json_mode=False)
def make_pydantic_openai_model(config: RuntimeConfig) -> Any | None:
"""Create a PydanticAI OpenAI-compatible model when the library is present."""
try:
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider
provider = OpenAIProvider(base_url=config.base_url, api_key=config.api_key)
return OpenAIChatModel(config.model, provider=provider)
except Exception:
pass
try:
from pydantic_ai.models.openai import OpenAIModel
from pydantic_ai.providers.openai import OpenAIProvider
provider = OpenAIProvider(base_url=config.base_url, api_key=config.api_key)
return OpenAIModel(config.model, provider=provider)
except Exception:
return None
def make_pydantic_agent(model: Any, output_model: type[Any], system_prompt: str) -> Any | None:
"""Compatibility wrapper for PydanticAI versions.
Some environments use `result_type`, newer ones may use `output_type`. The
notebook hit this exact API mismatch, so the submission code adapts at runtime.
"""
if model is None:
return None
try:
from pydantic_ai import Agent
signature = inspect.signature(Agent)
kwargs: dict[str, Any] = {}
if "output_type" in signature.parameters:
kwargs["output_type"] = output_model
elif "result_type" in signature.parameters:
kwargs["result_type"] = output_model
if "instructions" in signature.parameters:
kwargs["instructions"] = system_prompt
elif "system_prompt" in signature.parameters:
kwargs["system_prompt"] = system_prompt
if "model" in signature.parameters:
kwargs["model"] = model
return Agent(**kwargs)
return Agent(model, **kwargs)
except Exception:
return None