mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-05-01 23:16:31 +02:00
# 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.
157 lines
5.1 KiB
Python
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)
|