110 lines
3.5 KiB
Python
110 lines
3.5 KiB
Python
from pathlib import Path
|
|
from string import Template
|
|
|
|
import pytest
|
|
from pydantic import BaseModel
|
|
|
|
import agentci.prompts
|
|
from agentci.opencode import load_schema
|
|
from agentci.prompts import PromptLibrary
|
|
from agentci.workflows.model import (
|
|
AgentResult,
|
|
DiscussionReply,
|
|
PlanArtifact,
|
|
ReviewFinding,
|
|
ReviewReport,
|
|
)
|
|
|
|
PROMPTS = {
|
|
"plan_initial": {"context", "request"},
|
|
"plan_review": {"context", "artifact"},
|
|
"plan_revision": {"artifact", "review"},
|
|
"discuss": {"artifact", "message"},
|
|
"plan_iterate": {"context", "artifact", "review", "message"},
|
|
"implement_initial": {
|
|
"context",
|
|
"artifact",
|
|
"request",
|
|
"development_environment",
|
|
},
|
|
"implementation_review": {"issue_context", "artifact", "pull_context"},
|
|
"implementation_revision": {"review", "development_environment"},
|
|
"implementation_iterate": {
|
|
"context",
|
|
"review",
|
|
"message",
|
|
"development_environment",
|
|
},
|
|
"fix": {"context", "message", "development_environment"},
|
|
}
|
|
|
|
SCHEMAS: dict[str, tuple[type[BaseModel], set[str]]] = {
|
|
"plan.json": (PlanArtifact, {"plan_markdown"}),
|
|
"discussion.json": (DiscussionReply, {"markdown"}),
|
|
"agent_result.json": (AgentResult, {"summary_markdown", "tests"}),
|
|
"review.json": (ReviewReport, {"summary", "findings"}),
|
|
}
|
|
|
|
|
|
def prompt_directory() -> Path:
|
|
module_file = agentci.prompts.__file__
|
|
assert module_file is not None
|
|
return Path(module_file).parent
|
|
|
|
|
|
@pytest.mark.parametrize(("name", "identifiers"), PROMPTS.items())
|
|
def test_referenced_prompt_loads_and_renders(name: str, identifiers: set[str]) -> None:
|
|
directory = prompt_directory()
|
|
source = (directory / f"{name}.md").read_text()
|
|
values = {identifier: f"<{identifier}-value>" for identifier in identifiers}
|
|
|
|
assert set(Template(source).get_identifiers()) == identifiers
|
|
rendered = PromptLibrary(directory).render(name, **values)
|
|
for value in values.values():
|
|
assert value in rendered
|
|
|
|
|
|
def test_prompt_resource_set_matches_workflow_references() -> None:
|
|
names = {path.stem for path in prompt_directory().glob("*.md")}
|
|
|
|
assert names == set(PROMPTS)
|
|
|
|
|
|
def test_prompt_render_rejects_missing_template_value() -> None:
|
|
with pytest.raises(KeyError, match="request"):
|
|
PromptLibrary(prompt_directory()).render("plan_initial", context="context")
|
|
|
|
|
|
@pytest.mark.parametrize(("name", "contract"), SCHEMAS.items())
|
|
def test_referenced_schema_matches_result_model(
|
|
name: str, contract: tuple[type[BaseModel], set[str]]
|
|
) -> None:
|
|
model, required = contract
|
|
schema = load_schema(prompt_directory() / "schemas", name)
|
|
|
|
assert schema["type"] == "object"
|
|
assert schema["additionalProperties"] is False
|
|
assert set(schema["properties"]) == set(model.model_fields)
|
|
assert set(schema["required"]) == required
|
|
|
|
|
|
def test_review_finding_schema_matches_model_and_severity_values() -> None:
|
|
schema = load_schema(prompt_directory() / "schemas", "review.json")
|
|
finding = schema["properties"]["findings"]["items"]
|
|
|
|
assert set(finding["properties"]) == set(ReviewFinding.model_fields)
|
|
assert set(finding["required"]) == set(ReviewFinding.model_fields)
|
|
assert finding["properties"]["severity"]["enum"] == [
|
|
"blocking",
|
|
"major",
|
|
"minor",
|
|
]
|
|
assert finding["additionalProperties"] is False
|
|
|
|
|
|
def test_schema_resource_set_matches_workflow_references() -> None:
|
|
schema_directory = prompt_directory() / "schemas"
|
|
names = {path.name for path in schema_directory.glob("*.json")}
|
|
|
|
assert names == set(SCHEMAS)
|