Files
Stirling-PDF/engine/tests/test_user_spec_agent.py
James Brunton 2a856fbc19 Allow chat history to be sent to AI engine (#6128)
# Description of Changes
Add an extra parameter to every agent to receive the conversation
history in addition to the current message. This will make it possible
to answer followup questions from the AI without needing to give full
context in your message.
2026-04-21 15:03:10 +00:00

157 lines
5.1 KiB
Python

from __future__ import annotations
import pytest
from pydantic import ValidationError
from stirling.agents import UserSpecAgent
from stirling.contracts import (
AgentDraft,
AgentDraftRequest,
AgentRevisionRequest,
ConversationMessage,
EditClarificationRequest,
EditPlanResponse,
ToolOperationStep,
)
from stirling.models.tool_models import Angle, FlattenParams, RotatePdfParams, ToolEndpoint
from stirling.services.runtime import AppRuntime
class StubUserSpecAgent(UserSpecAgent):
def __init__(self, runtime: AppRuntime, draft_result: AgentDraft, revision_result: AgentDraft) -> None:
super().__init__(runtime)
self.draft_result = draft_result
self.revision_result = revision_result
self.edit_plan = EditPlanResponse(
summary="Rotate the document.",
steps=[
ToolOperationStep(
tool=ToolEndpoint.ROTATE_PDF,
parameters=RotatePdfParams(angle=Angle(90)),
)
],
)
async def _build_edit_plan(
self, user_message: str, conversation_history: list[ConversationMessage]
) -> EditPlanResponse:
return self.edit_plan
async def _run_draft_agent(self, request: AgentDraftRequest, edit_plan: EditPlanResponse) -> AgentDraft:
return self.draft_result
async def _run_revision_agent(self, request: AgentRevisionRequest, edit_plan: EditPlanResponse) -> AgentDraft:
return self.revision_result
class ClarifyingUserSpecAgent(UserSpecAgent):
def __init__(self, runtime: AppRuntime) -> None:
super().__init__(runtime)
async def _build_edit_plan(
self, user_message: str, conversation_history: list[ConversationMessage]
) -> EditClarificationRequest:
return EditClarificationRequest(
question="Which pages should be changed?",
reason="The request does not specify the target pages.",
)
@pytest.mark.anyio
async def test_user_spec_agent_drafts_agent_spec(runtime: AppRuntime) -> None:
agent = StubUserSpecAgent(
runtime,
AgentDraft(
name="Invoice Cleanup",
description="Prepare invoices for review.",
objective="Normalize invoices before accounting review.",
steps=[
ToolOperationStep(
tool=ToolEndpoint.ROTATE_PDF,
parameters=RotatePdfParams(angle=Angle(90)),
)
],
),
revision_result=AgentDraft(
name="Unused",
description="Unused",
objective="Unused",
steps=[],
),
)
response = await agent.draft(
AgentDraftRequest(
user_message="Build me an invoice cleanup agent.",
conversation_history=[
ConversationMessage(role="user", content="It should handle scanned PDFs."),
],
)
)
assert response.outcome == "draft"
assert response.draft.name == "Invoice Cleanup"
assert response.draft.steps[0].kind == "tool"
@pytest.mark.anyio
async def test_user_spec_agent_revises_existing_draft(runtime: AppRuntime) -> None:
current_draft = AgentDraft(
name="Invoice Cleanup",
description="Prepare invoices for review.",
objective="Normalize invoices before accounting review.",
steps=[
ToolOperationStep(
tool=ToolEndpoint.ROTATE_PDF,
parameters=RotatePdfParams(angle=Angle(90)),
)
],
)
agent = StubUserSpecAgent(
runtime,
draft_result=current_draft,
revision_result=AgentDraft(
name="Invoice Cleanup",
description="Prepare invoices for review and reduce file size.",
objective="Normalize invoices before accounting review.",
steps=[
ToolOperationStep(
tool=ToolEndpoint.ROTATE_PDF,
parameters=RotatePdfParams(angle=Angle(90)),
),
ToolOperationStep(
tool=ToolEndpoint.FLATTEN,
parameters=FlattenParams(flatten_only_forms=False, render_dpi=None),
),
],
),
)
response = await agent.revise(
AgentRevisionRequest(
user_message="Also compress the files before upload.",
current_draft=current_draft,
)
)
assert response.outcome == "draft"
assert len(response.draft.steps) == 2
assert response.draft.steps[1].kind == "tool"
def test_tool_operation_step_rejects_mismatched_parameters() -> None:
with pytest.raises(ValidationError):
ToolOperationStep(
tool=ToolEndpoint.ROTATE_PDF,
parameters=FlattenParams(flatten_only_forms=False, render_dpi=None),
)
@pytest.mark.anyio
async def test_user_spec_agent_propagates_edit_clarification(runtime: AppRuntime) -> None:
agent = ClarifyingUserSpecAgent(runtime)
response = await agent.draft(AgentDraftRequest(user_message="Build an agent to rotate some pages."))
assert isinstance(response, EditClarificationRequest)