vignette/apps/api/app/services/continuous_improvement_agentic.py
Yun Chan 16e791e044 G0~G8 성과·동맹 측정 OS 작업 일괄 고정
8월 7일까지 워킹트리에만 남아 있던 미커밋 작업을 커밋한다. 여러 사본
폴더(worktree·clone)에 흩어져 있던 중간 스냅샷을 정리하기 전에 원본을
git 이력으로 고정하는 것이 목적이다.

- contracts/routes/services: measurement, outcome_trajectory, rupture_repair,
  deliberate_practice, calibration_transfer, supervision_research,
  multimodal_alliance, continuous_improvement 계열 신규 모듈과 테스트
- infra/db/init: 07~16 마이그레이션(측정 기반~calibration transfer 실행)
- apps/web: 세션 리뷰 카드·관리 화면·E2E 스펙 추가
- docs/ops: G0~G8 라이브 통합·배포·롤백 증거 문서와 evidence JSON/PNG
- scripts: smoke·ledger·릴리스 에이전트·NAS 프리뷰 운영 스크립트

engine.public 로그 .bak과 apps/web/test-results 산출물은 커밋에서 제외했다.
2026-08-08 01:30:53 +09:00

817 lines
31 KiB
Python

"""G8 source pack -> draft -> red-team -> benchmark agentic worker.
The existing continuous-improvement store is deliberately the only persistence
boundary. This worker owns model calls and local fail-closed validation, then
hands the resulting immutable metadata to that store only after every agent
stage has succeeded.
"""
from __future__ import annotations
import asyncio
import hashlib
import json
import re
from collections.abc import Mapping, Sequence
from typing import Any, Protocol
from urllib.parse import urlsplit
from uuid import UUID
import httpx
from pydantic import BaseModel, ConfigDict, Field, ValidationError, model_validator
from ..contracts.continuous_improvement import (
ContentBenchmarkQualification,
ContentSourceArtifact,
GeneratedContentDraft,
IndependentRedTeamReview,
OperationalIncident,
RedTeamFinding,
ReviewDimension,
)
from ..contracts.engine_gateway import (
EngineMessage,
GenerateRequest,
GenerateResponse,
structured_payload_from_response,
)
from . import continuous_improvement_store
from .guardrail import mask_pii, mask_synthetic_generated_pii
PROMPT_VERSION = "1.0.0"
_TOKEN_PATTERN = re.compile(r"[A-Za-z0-9가-힣_]{4,}")
_REVIEW_LANES: tuple[tuple[str, tuple[ReviewDimension, ...]], ...] = (
("safety-identity", ("safety", "identity", "pii", "grounding")),
("leakage-bias", ("answer_leakage", "cultural_bias", "difficulty")),
)
class AgenticPipelineError(ValueError):
"""Base error for the model-owned content pipeline."""
class AgenticPipelineExecutionError(AgenticPipelineError):
"""A required model call or structured response failed."""
class AgenticPipelineRejectedError(AgenticPipelineError):
"""Generated content failed a safety, grounding, or benchmark gate."""
class StructuredGenerationEngine(Protocol):
async def generate(self, req: GenerateRequest) -> GenerateResponse: ...
class HttpRollbackExecutor:
"""Authenticated control-plane adapter for model and runtime rollback."""
TOKEN_HEADER = "X-Vignette-Rollback-Executor-Token"
def __init__(
self,
*,
endpoint: str,
token: str,
timeout_seconds: float,
transport: httpx.AsyncBaseTransport | None = None,
) -> None:
parsed = urlsplit(endpoint)
is_local_http = parsed.scheme == "http" and (parsed.hostname or "").lower() in {
"localhost",
"127.0.0.1",
"::1",
}
if parsed.scheme != "https" and not is_local_http:
raise ValueError("rollback executor endpoint must use HTTPS")
if parsed.username or parsed.password or parsed.query or parsed.fragment:
raise ValueError("rollback executor endpoint must not contain credentials or query")
if len(token) < 32:
raise ValueError("rollback executor token must contain at least 32 characters")
self._endpoint = endpoint
self._token = token
self._timeout_seconds = timeout_seconds
self._transport = transport
async def execute(
self, request: continuous_improvement_store.RollbackExecutionRequest
) -> continuous_improvement_store.RollbackExecutionReceipt:
try:
async with httpx.AsyncClient(
timeout=httpx.Timeout(self._timeout_seconds),
follow_redirects=False,
trust_env=False,
transport=self._transport,
) as client:
response = await client.post(
self._endpoint,
headers={
self.TOKEN_HEADER: self._token,
"Accept": "application/json",
},
json=request.model_dump(mode="json"),
)
response.raise_for_status()
if len(response.content) > 65_536:
raise ValueError("rollback executor receipt is too large")
payload = response.json()
except (httpx.HTTPError, ValueError) as exc:
raise AgenticPipelineExecutionError(
"rollback executor request failed"
) from exc
return continuous_improvement_store.RollbackExecutionReceipt.model_validate(payload)
def build_configured_rollback_executor(
settings: Any,
) -> continuous_improvement_store.RollbackExecutor | None:
"""Return the separately opted-in executor; disabled always means no call."""
if not bool(settings.continuous_improvement_rollback_executor_enabled):
return None
token = settings.continuous_improvement_rollback_executor_token.get_secret_value()
return HttpRollbackExecutor(
endpoint=settings.continuous_improvement_rollback_executor_endpoint,
token=token,
timeout_seconds=settings.continuous_improvement_rollback_executor_timeout_seconds,
)
class AgenticSourcePack(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
artifact: ContentSourceArtifact
content: str = Field(min_length=1, max_length=100_000)
class GroundedClaim(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
claim: str = Field(min_length=1, max_length=600)
source_ref: str = Field(min_length=1, max_length=180)
class GeneratedDraftPayload(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
title: str = Field(min_length=1, max_length=180)
synthetic_profile: str = Field(min_length=1, max_length=1200)
scenario: str = Field(min_length=1, max_length=6000)
rupture_or_challenge: str = Field(min_length=1, max_length=2400)
learner_task: str = Field(min_length=1, max_length=2000)
success_criteria: tuple[str, ...] = Field(min_length=1, max_length=10)
hidden_answer: str = Field(min_length=4, max_length=2000)
source_refs: tuple[str, ...] = Field(min_length=1, max_length=100)
grounded_claims: tuple[GroundedClaim, ...] = Field(min_length=1, max_length=20)
@model_validator(mode="after")
def unique_source_refs(self) -> "GeneratedDraftPayload":
if len(self.source_refs) != len(set(self.source_refs)):
raise ValueError("generated draft source refs must be unique")
return self
def visible_payload(self) -> dict[str, Any]:
payload = self.model_dump(mode="json")
payload.pop("hidden_answer", None)
return payload
class RedTeamFindingOutput(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
dimension: ReviewDimension
severity: str = Field(pattern=r"^(blocker|high|moderate|low)$")
evidence_summary: str = Field(min_length=1, max_length=500)
class RedTeamOutput(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
dimensions: tuple[ReviewDimension, ...] = Field(min_length=3)
findings: tuple[RedTeamFindingOutput, ...] = Field(max_length=50)
class BenchmarkVariantOutput(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
visible_scenario: str = Field(min_length=1, max_length=5000)
learner_task: str = Field(min_length=1, max_length=2000)
success_criteria: tuple[str, ...] = Field(min_length=1, max_length=10)
hidden_answer: str = Field(min_length=4, max_length=2000)
source_refs: tuple[str, ...] = Field(min_length=1, max_length=100)
def visible_payload(self) -> dict[str, Any]:
payload = self.model_dump(mode="json")
payload.pop("hidden_answer", None)
return payload
class BenchmarkVariantPackOutput(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
variants: tuple[BenchmarkVariantOutput, ...] = Field(min_length=3, max_length=12)
class BenchmarkJudgeOutput(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
passed: bool
answer_leakage: bool
pii_found: bool
unsupported_claim: bool
safety_failure: bool
reward_hacking: bool
evidence_summary: str = Field(min_length=1, max_length=500)
@model_validator(mode="after")
def consistent_pass_flag(self) -> "BenchmarkJudgeOutput":
has_failure = any(
(
self.answer_leakage,
self.pii_found,
self.unsupported_claim,
self.safety_failure,
self.reward_hacking,
)
)
if self.passed == has_failure:
raise ValueError("benchmark judge pass flag contradicts failure flags")
return self
class AgenticPipelineResult(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
submission_id: UUID
pipeline_id: UUID
qualification_id: UUID
candidate_catalog_entry_id: str
state: str
human_approval_required: bool
catalog_promoted: bool
idempotent_replay: bool
clinical_claim_allowed: bool
draft_id: str
benchmark_id: str
red_team_review_count: int
benchmark_variant_count: int
agent_calls_executed: int
trigger_kind: str
def _canonical_json(value: Any) -> str:
return json.dumps(
value,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
default=str,
)
def _sha256_text(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()
def _answer_overlap_count(hidden_answer: str, visible_payload: Mapping[str, Any]) -> int:
hidden_tokens = [item.lower() for item in _TOKEN_PATTERN.findall(hidden_answer)]
visible_tokens = [
item.lower()
for item in _TOKEN_PATTERN.findall(_canonical_json(visible_payload))
]
hidden_set = set(hidden_tokens)
visible_set = set(visible_tokens)
opaque_markers = {
token
for token in hidden_set
if "_" in token or any(character.isdigit() for character in token)
}
def shingles(tokens: Sequence[str], width: int = 4) -> set[tuple[str, ...]]:
return {
tuple(tokens[index : index + width])
for index in range(max(0, len(tokens) - width + 1))
}
marker_overlap = opaque_markers & visible_set
phrase_overlap = shingles(hidden_tokens) & shingles(visible_tokens)
return len(marker_overlap) + len(phrase_overlap)
def _assert_pii_free(value: Any, *, stage: str) -> None:
result = mask_synthetic_generated_pii(_canonical_json(value))
if result.entities:
entity_types = ",".join(sorted(set(result.entities)))
raise AgenticPipelineRejectedError(
f"{stage} contains PII types: {entity_types}"
)
def validate_source_packs(
source_packs: Sequence[AgenticSourcePack],
) -> list[dict[str, Any]]:
"""Validate source approval, integrity and PII before any model call."""
if not source_packs:
raise AgenticPipelineRejectedError("at least one source pack is required")
source_ids = [item.artifact.source_id for item in source_packs]
if len(source_ids) != len(set(source_ids)):
raise AgenticPipelineRejectedError("source pack ids must be unique")
context: list[dict[str, Any]] = []
for item in source_packs:
if item.artifact.usage_status != "approved":
raise AgenticPipelineRejectedError("agentic generation requires approved sources")
if _sha256_text(item.content) != item.artifact.content_sha256:
raise AgenticPipelineRejectedError("source pack content hash mismatch")
masked = mask_pii(item.content)
if masked.entities:
raise AgenticPipelineRejectedError("source pack contains PII")
context.append(
{
"source_id": item.artifact.source_id,
"version": item.artifact.version,
"content_sha256": item.artifact.content_sha256,
"provenance_uri": item.artifact.provenance_uri,
"citation_label": item.artifact.citation_label,
"content": item.content,
}
)
return context
# Backward-compatible private seam retained for focused contract tests.
_source_context = validate_source_packs
def _generation_prompt_payload(
*,
source_context: Sequence[Mapping[str, Any]],
content_kind: str,
difficulty_level: int,
prompt_version: str,
trigger_kind: str,
) -> dict[str, Any]:
return {
"prompt_version": prompt_version,
"content_kind": content_kind,
"difficulty_level": difficulty_level,
"trigger_kind": trigger_kind,
"sources": list(source_context),
}
def source_pack_from_operational_incident(
incident: OperationalIncident,
) -> AgenticSourcePack:
"""Convert persisted metadata-only operational failure into adversarial input."""
content = _canonical_json(incident.model_dump(mode="json"))
fingerprint = incident.error_fingerprint[:24]
return AgenticSourcePack(
artifact=ContentSourceArtifact(
source_id=f"oas-g8-source-incident-{fingerprint}",
version="1.0.0",
content_sha256=_sha256_text(content),
provenance_uri=(
"audit://continuous-improvement/incidents/"
f"{incident.incident_id}"
),
usage_status="approved",
citation_label=(
"운영 오류 재현용 metadata source: "
f"{incident.affected_contract}"
),
),
content=content,
)
async def _generate_structured(
*,
engine: StructuredGenerationEngine,
stage: str,
agent_id: str,
messages: list[EngineMessage],
schema_model: type[BaseModel],
pipeline_id: UUID,
max_tokens: int = 2400,
) -> tuple[BaseModel, GenerateResponse, int]:
repair_message = EngineMessage(
role="user",
content=(
"직전 출력이 JSON schema 검증에 실패했다. 동일한 작업을 다시 수행하되, "
"제공된 JSON schema를 정확히 만족하는 단일 JSON object만 반환하라. "
"필수 필드를 모두 포함하고 타입과 enum을 지키며 설명이나 markdown을 덧붙이지 마라. "
"내용 정책, 승인된 source 범위, 안전 경계는 바꾸지 마라."
),
)
last_error_message = f"{stage} returned no structured output"
last_validation_error: ValidationError | None = None
max_attempts = 2
for attempt in range(1, max_attempts + 1):
request_messages = messages if attempt == 1 else [*messages, repair_message]
request = GenerateRequest(
ai_role="evaluator",
messages=request_messages,
max_tokens=max_tokens,
temperature=0.1 if attempt == 1 else 0.0,
structured_schema=schema_model.model_json_schema(),
metadata={
"purpose": "g8_agentic_content_pipeline",
"agentic_stage": stage,
"agent_id": agent_id,
"pipeline_id": str(pipeline_id),
"structured_attempt": attempt,
"structured_repair": attempt > 1,
},
)
try:
response = await engine.generate(request)
except Exception as exc:
raise AgenticPipelineExecutionError(f"{stage} model call failed") from exc
payload = structured_payload_from_response(response)
if payload is None:
last_error_message = f"{stage} returned no structured output"
continue
try:
parsed = schema_model.model_validate(payload)
except ValidationError as exc:
last_error_message = f"{stage} returned invalid structured output"
last_validation_error = exc
continue
return parsed, response, attempt
error = AgenticPipelineExecutionError(last_error_message)
if last_validation_error is not None:
raise error from last_validation_error
raise error
def _validate_grounding(
source_ids: set[str], source_refs: Sequence[str], claims: Sequence[GroundedClaim]
) -> int:
unknown = set(source_refs) - source_ids
unknown.update(item.source_ref for item in claims if item.source_ref not in source_ids)
return len(unknown)
async def run_agentic_content_pipeline(
*,
conn: Any,
engine: StructuredGenerationEngine,
submission_id: UUID,
pipeline_id: UUID,
benchmark_record_id: UUID,
qualification_id: UUID,
source_packs: Sequence[AgenticSourcePack],
content_kind: str,
difficulty_level: int,
variant_count: int,
prompt_version: str = PROMPT_VERSION,
trigger_kind: str = "source_pack",
) -> AgenticPipelineResult:
if content_kind not in {"case", "rupture", "practice", "benchmark"}:
raise AgenticPipelineRejectedError("unsupported content kind")
if not 1 <= difficulty_level <= 5:
raise AgenticPipelineRejectedError("difficulty level must be between 1 and 5")
if not 3 <= variant_count <= 12:
raise AgenticPipelineRejectedError("variant count must be between 3 and 12")
sources = tuple(source_packs)
source_context = validate_source_packs(sources)
source_ids = {item.artifact.source_id for item in sources}
prompt_payload = _generation_prompt_payload(
source_context=source_context,
content_kind=content_kind,
difficulty_level=difficulty_level,
prompt_version=prompt_version,
trigger_kind=trigger_kind,
)
prompt_sha256 = _sha256_text(_canonical_json(prompt_payload))
replay = await continuous_improvement_store.find_content_pipeline_submission(
conn, submission_id=submission_id
)
if replay is not None:
expected = {
"pipeline_id": str(pipeline_id),
"benchmark_record_id": str(benchmark_record_id),
"qualification_id": str(qualification_id),
"prompt_sha256": prompt_sha256,
}
actual = {key: str(replay.get(key)) for key in expected}
if actual != expected:
raise continuous_improvement_store.ContinuousImprovementConflictError(
"submission id was already used with different agentic input"
)
return AgenticPipelineResult(
submission_id=submission_id,
pipeline_id=pipeline_id,
qualification_id=qualification_id,
candidate_catalog_entry_id=str(replay["candidate_catalog_entry_id"]),
state="pending_human_approval",
human_approval_required=True,
catalog_promoted=False,
idempotent_replay=True,
clinical_claim_allowed=False,
draft_id=str(replay["draft_id"]),
benchmark_id=str(replay["benchmark_id"]),
red_team_review_count=int(replay["red_team_review_count"]),
benchmark_variant_count=int(replay["benchmark_variant_count"]),
agent_calls_executed=0,
trigger_kind=trigger_kind,
)
slug = pipeline_id.hex
generation_messages = [
EngineMessage(
role="system",
content=(
"너는 교육용 상담 시뮬레이션 콘텐츠 생성 agent다. 승인된 source만 사용하고 "
"실존 또는 합성 고유 이름, 이니셜, 기관, 주소, 연락처, 구체 날짜, 금액, 식별 번호를 "
"절대 만들지 마라. 등장인물은 오직 'fictional client''learner' 같은 역할명으로만 "
"지칭하라. hidden_answer의 고유 marker나 연속된 답안 구절은 visible 필드에 "
"반복하지 말고, 모든 grounding claim은 입력 source_id를 참조하라. 출력은 schema만 따른다."
),
),
EngineMessage(role="user", content=_canonical_json(prompt_payload)),
]
generated_model, generation_response, generation_attempts = await _generate_structured(
engine=engine,
stage="draft_generation",
agent_id="g8-content-generator-v1",
messages=generation_messages,
schema_model=GeneratedDraftPayload,
pipeline_id=pipeline_id,
)
agent_calls_executed = generation_attempts
generated = GeneratedDraftPayload.model_validate(generated_model.model_dump())
unsupported_claims = _validate_grounding(
source_ids, generated.source_refs, generated.grounded_claims
)
if unsupported_claims:
raise AgenticPipelineRejectedError("generated draft has unsupported claims")
_assert_pii_free(generated.model_dump(mode="json"), stage="generated draft")
answer_overlap = _answer_overlap_count(
generated.hidden_answer, generated.visible_payload()
)
if answer_overlap:
raise AgenticPipelineRejectedError("generated draft exposes hidden answer tokens")
payload_sha256 = _sha256_text(_canonical_json(generated.model_dump(mode="json")))
draft_id = f"oas-g8-draft-{slug}"
draft = GeneratedContentDraft(
draft_id=draft_id,
content_kind=content_kind,
source_refs=tuple(generated.source_refs),
generation_model=f"{generation_response.provider}/{generation_response.model}",
prompt_version=prompt_version,
prompt_sha256=prompt_sha256,
payload_sha256=payload_sha256,
synthetic_identity_id=f"synthetic-identity-{slug}",
difficulty_level=difficulty_level,
hidden_answer_fingerprint=_sha256_text(generated.hidden_answer),
visible_answer_overlap_tokens=0,
pii_findings=0,
unsupported_clinical_claims=0,
)
async def review_lane(
lane: str, dimensions: tuple[ReviewDimension, ...]
) -> tuple[IndependentRedTeamReview, int]:
agent_id = f"g8-redteam-{lane}-v1"
messages = [
EngineMessage(
role="system",
content=(
"너는 다른 reviewer와 대화하거나 결과를 공유하지 않는 독립 red-team agent다. "
f"오직 {','.join(dimensions)} 차원만 공격적으로 검토하라. 문제를 찾으면 finding을 "
"반드시 남기되, source와 draft에서 입증되는 실제 계약 위반만 finding이다. 선택적 문구 개선, "
"취향, 근거 없는 가능성은 finding으로 만들지 말고 실제 위반이 없으면 findings=[]를 반환하라. "
"출력은 schema만 따른다."
),
),
EngineMessage(
role="user",
content=_canonical_json(
{
"draft": generated.model_dump(mode="json"),
"sources": source_context,
"reviewed_payload_sha256": payload_sha256,
}
),
),
]
output_model, _, attempts = await _generate_structured(
engine=engine,
stage=f"red_team_{lane}",
agent_id=agent_id,
messages=messages,
schema_model=RedTeamOutput,
pipeline_id=pipeline_id,
)
output = RedTeamOutput.model_validate(output_model.model_dump())
if set(output.dimensions) != set(dimensions):
raise AgenticPipelineExecutionError(
f"red_team_{lane} did not attest assigned dimensions"
)
if any(item.dimension not in dimensions for item in output.findings):
raise AgenticPipelineExecutionError(
f"red_team_{lane} returned a cross-lane finding"
)
findings = tuple(
RedTeamFinding(
finding_id=f"oas-g8-finding-{slug}-{lane}-{index}",
dimension=item.dimension,
severity=item.severity,
state="open",
evidence_ref=(
f"audit://continuous-improvement/{slug}/redteam/{lane}/{index}"
),
)
for index, item in enumerate(output.findings, start=1)
)
return (
IndependentRedTeamReview(
review_id=f"oas-g8-review-{slug}-{lane}",
draft_id=draft_id,
reviewer_agent_id=agent_id,
dimensions=dimensions,
findings=findings,
reviewed_payload_sha256=payload_sha256,
),
attempts,
)
review_results = tuple(
await asyncio.gather(
*(review_lane(lane, dimensions) for lane, dimensions in _REVIEW_LANES)
)
)
reviews = tuple(review for review, _ in review_results)
agent_calls_executed += sum(attempts for _, attempts in review_results)
if any(review.findings for review in reviews):
finding_summary = sorted(
f"{finding.dimension}:{finding.severity}"
for review in reviews
for finding in review.findings
)
raise AgenticPipelineRejectedError(
"red-team agents found unresolved issues: " + ",".join(finding_summary)
)
variant_messages = [
EngineMessage(
role="system",
content=(
f"원본 교육 초안을 의미는 유지하되 표현·관계 맥락·난이도를 바꾼 {variant_count}"
"적대 benchmark variant로 변형하라. 각 hidden_answer 고유 marker나 연속 답안 구절은 visible 필드에 "
"반복하지 말고 입력 source_id만 참조하라. 출력은 schema만 따른다."
),
),
EngineMessage(
role="user",
content=_canonical_json(
{"draft": generated.model_dump(mode="json"), "sources": source_context}
),
),
]
variant_model, _, variant_attempts = await _generate_structured(
engine=engine,
stage="benchmark_variant_generation",
agent_id="g8-benchmark-variant-generator-v1",
messages=variant_messages,
schema_model=BenchmarkVariantPackOutput,
pipeline_id=pipeline_id,
)
agent_calls_executed += variant_attempts
variant_pack = BenchmarkVariantPackOutput.model_validate(variant_model.model_dump())
if len(variant_pack.variants) != variant_count:
raise AgenticPipelineExecutionError(
"benchmark variant generator returned an unexpected variant count"
)
for index, variant in enumerate(variant_pack.variants, start=1):
_assert_pii_free(variant.model_dump(mode="json"), stage=f"benchmark variant {index}")
if set(variant.source_refs) - source_ids:
raise AgenticPipelineRejectedError(
f"benchmark variant {index} has unsupported claims"
)
if _answer_overlap_count(variant.hidden_answer, variant.visible_payload()):
raise AgenticPipelineRejectedError(
f"benchmark variant {index} exposes hidden answer tokens"
)
async def judge_variant(
index: int, variant: BenchmarkVariantOutput
) -> tuple[BenchmarkJudgeOutput, int]:
messages = [
EngineMessage(
role="system",
content=(
"너는 독립 benchmark judge다. answer leakage, PII, 무근거 임상 주장, 안전 실패, "
"점수·문구 암기 보상 해킹을 각각 판정하라. 하나라도 있으면 passed=false다. "
"출력은 schema만 따른다."
),
),
EngineMessage(
role="user",
content=_canonical_json(
{
"source_ids": sorted(source_ids),
"original_draft": generated.model_dump(mode="json"),
"variant": variant.model_dump(mode="json"),
}
),
),
]
output_model, _, attempts = await _generate_structured(
engine=engine,
stage=f"benchmark_judge_{index}",
agent_id=f"g8-benchmark-judge-{index}-v1",
messages=messages,
schema_model=BenchmarkJudgeOutput,
pipeline_id=pipeline_id,
)
return BenchmarkJudgeOutput.model_validate(output_model.model_dump()), attempts
judgment_results = tuple(
await asyncio.gather(
*(
judge_variant(index, variant)
for index, variant in enumerate(variant_pack.variants, start=1)
)
)
)
judgments = tuple(judgment for judgment, _ in judgment_results)
agent_calls_executed += sum(attempts for _, attempts in judgment_results)
pass_count = sum(1 for item in judgments if item.passed)
benchmark_id = f"oas-g8-benchmark-{slug}"
benchmark = ContentBenchmarkQualification(
benchmark_id=benchmark_id,
draft_id=draft_id,
variant_count=len(judgments),
variant_pass_rate=pass_count / len(judgments),
answer_leakage_count=sum(1 for item in judgments if item.answer_leakage),
pii_finding_count=sum(1 for item in judgments if item.pii_found),
unsupported_claim_count=sum(1 for item in judgments if item.unsupported_claim),
safety_failure_count=sum(1 for item in judgments if item.safety_failure),
reward_hacking_count=sum(1 for item in judgments if item.reward_hacking),
evidence_refs=tuple(
f"audit://continuous-improvement/{slug}/benchmark/judge/{index}"
for index in range(1, len(judgments) + 1)
),
)
if not benchmark.qualified:
raise AgenticPipelineRejectedError("benchmark qualification failed")
stored = await continuous_improvement_store.submit_content_pipeline(
conn,
submission_id=submission_id,
pipeline_id=pipeline_id,
benchmark_record_id=benchmark_record_id,
qualification_id=qualification_id,
draft=draft,
sources=[item.artifact for item in sources],
reviews=list(reviews),
benchmark=benchmark,
draft_payload=generated.model_dump(mode="json"),
)
if (
stored.get("state") != "pending_human_approval"
or stored.get("human_approval_required") is not True
or stored.get("catalog_promoted") is not False
or stored.get("clinical_claim_allowed") is not False
):
raise AgenticPipelineExecutionError(
"content store violated the mandatory human approval boundary"
)
return AgenticPipelineResult(
**stored,
draft_id=draft_id,
benchmark_id=benchmark_id,
red_team_review_count=len(reviews),
benchmark_variant_count=len(judgments),
agent_calls_executed=agent_calls_executed,
trigger_kind=trigger_kind,
)
__all__ = [
"AgenticPipelineError",
"AgenticPipelineExecutionError",
"AgenticPipelineRejectedError",
"AgenticPipelineResult",
"AgenticSourcePack",
"BenchmarkJudgeOutput",
"BenchmarkVariantOutput",
"BenchmarkVariantPackOutput",
"GeneratedDraftPayload",
"GroundedClaim",
"HttpRollbackExecutor",
"PROMPT_VERSION",
"RedTeamOutput",
"build_configured_rollback_executor",
"run_agentic_content_pipeline",
"source_pack_from_operational_incident",
"validate_source_packs",
]