mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
Change AI engine to execute tools in Java instead of on frontend (#6116)
# Description of Changes Redesign AI engine so that it autogenerates the `tool_models.py` file from the OpenAPI spec so the Python has access to the Java API parameters and the full list of Java tools that it can run. CI ensures that whenever someone modifies a tool endpoint that the AI enigne tool models get updated as well (the dev gets told to run `task engine:tool-models`). There's loads of advantages to having the Java be the one that actually executes the tools, rather than the frontend as it was previously set up to theoretically use: - The AI gets much better descriptions of the params from the API docs - It'll be usable headless in the future so a Java daemon could run to execute ops on files in a folder without the need for the UI to run - The Java already has all the logic it needs to execute the tools - We don't need to parse the TypeScript to find the API (which is hard because the TS wasn't designed to be computer-read to extract the API) I've also hooked up the prototype frontend to ensure it's working properly, and have built it in a way that all the tool names can be translated properly, which was always an issue with previous prototypes of this. --------- Co-authored-by: Anthony Stirling <[email protected]> Co-authored-by: EthanHealy01 <[email protected]>
This commit is contained in:
co-authored by
Anthony Stirling
EthanHealy01
parent
cc9650e7a3
commit
e5767ed58b
@@ -12,14 +12,14 @@ from stirling.contracts import (
|
||||
PdfEditRequest,
|
||||
ToolOperationStep,
|
||||
)
|
||||
from stirling.models.tool_models import CompressParams, OperationId, RotateParams
|
||||
from stirling.models.tool_models import Angle, FlattenParams, RotatePdfParams, ToolEndpoint
|
||||
from stirling.services.runtime import AppRuntime
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ParameterSelectorCall:
|
||||
request: PdfEditRequest
|
||||
operation_plan: list[OperationId]
|
||||
operation_plan: list[ToolEndpoint]
|
||||
operation_index: int
|
||||
generated_steps: list[ToolOperationStep]
|
||||
|
||||
@@ -31,10 +31,10 @@ class RecordingParameterSelector:
|
||||
async def select(
|
||||
self,
|
||||
request: PdfEditRequest,
|
||||
operation_plan: list[OperationId],
|
||||
operation_plan: list[ToolEndpoint],
|
||||
operation_index: int,
|
||||
generated_steps: list[ToolOperationStep],
|
||||
) -> RotateParams | CompressParams:
|
||||
) -> RotatePdfParams | FlattenParams:
|
||||
self.calls.append(
|
||||
ParameterSelectorCall(
|
||||
request=request,
|
||||
@@ -44,8 +44,8 @@ class RecordingParameterSelector:
|
||||
)
|
||||
)
|
||||
if operation_index == 0:
|
||||
return RotateParams(angle=90)
|
||||
return CompressParams(compression_level=5)
|
||||
return RotatePdfParams(angle=Angle(90))
|
||||
return FlattenParams(flatten_only_forms=False, render_dpi=None)
|
||||
|
||||
|
||||
class StubPdfEditAgent(PdfEditAgent):
|
||||
@@ -73,7 +73,7 @@ async def test_pdf_edit_agent_builds_multi_step_plan(runtime: AppRuntime) -> Non
|
||||
agent = StubPdfEditAgent(
|
||||
runtime,
|
||||
PdfEditPlanSelection(
|
||||
operations=[OperationId.ROTATE, OperationId.COMPRESS],
|
||||
operations=[ToolEndpoint.ROTATE_PDF, ToolEndpoint.FLATTEN],
|
||||
summary="Rotate the PDF, then compress it.",
|
||||
rationale="The pages need reorientation before reducing file size.",
|
||||
),
|
||||
@@ -90,9 +90,9 @@ async def test_pdf_edit_agent_builds_multi_step_plan(runtime: AppRuntime) -> Non
|
||||
assert isinstance(response, EditPlanResponse)
|
||||
assert response.summary == "Rotate the PDF, then compress it."
|
||||
assert response.rationale == "The pages need reorientation before reducing file size."
|
||||
assert [step.tool for step in response.steps] == [OperationId.ROTATE, OperationId.COMPRESS]
|
||||
assert isinstance(response.steps[0].parameters, RotateParams)
|
||||
assert isinstance(response.steps[1].parameters, CompressParams)
|
||||
assert [step.tool for step in response.steps] == [ToolEndpoint.ROTATE_PDF, ToolEndpoint.FLATTEN]
|
||||
assert isinstance(response.steps[0].parameters, RotatePdfParams)
|
||||
assert isinstance(response.steps[1].parameters, FlattenParams)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@@ -101,7 +101,7 @@ async def test_pdf_edit_agent_passes_previous_steps_to_parameter_selector(runtim
|
||||
agent = StubPdfEditAgent(
|
||||
runtime,
|
||||
PdfEditPlanSelection(
|
||||
operations=[OperationId.ROTATE, OperationId.COMPRESS],
|
||||
operations=[ToolEndpoint.ROTATE_PDF, ToolEndpoint.FLATTEN],
|
||||
summary="Rotate the PDF, then compress it.",
|
||||
),
|
||||
parameter_selector=parameter_selector,
|
||||
@@ -120,8 +120,8 @@ async def test_pdf_edit_agent_passes_previous_steps_to_parameter_selector(runtim
|
||||
assert parameter_selector.calls[1].operation_index == 1
|
||||
assert parameter_selector.calls[1].generated_steps == [
|
||||
ToolOperationStep(
|
||||
tool=OperationId.ROTATE,
|
||||
parameters=RotateParams(angle=90),
|
||||
tool=ToolEndpoint.ROTATE_PDF,
|
||||
parameters=RotatePdfParams(angle=Angle(90)),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ from stirling.contracts import (
|
||||
PdfQuestionNotFoundResponse,
|
||||
PdfQuestionRequest,
|
||||
)
|
||||
from stirling.models.tool_models import RotateParams
|
||||
from stirling.models.tool_models import Angle, RotatePdfParams
|
||||
|
||||
|
||||
class StubOrchestratorAgent:
|
||||
@@ -127,8 +127,8 @@ def test_agent_revise_route() -> None:
|
||||
"steps": [
|
||||
{
|
||||
"kind": "tool",
|
||||
"tool": "rotate",
|
||||
"parameters": RotateParams(angle=90).model_dump(by_alias=True),
|
||||
"tool": "/api/v1/general/rotate-pdf",
|
||||
"parameters": RotatePdfParams(angle=Angle(90)).model_dump(by_alias=True),
|
||||
}
|
||||
],
|
||||
},
|
||||
@@ -150,8 +150,8 @@ def test_next_action_route() -> None:
|
||||
"steps": [
|
||||
{
|
||||
"kind": "tool",
|
||||
"tool": "rotate",
|
||||
"parameters": RotateParams(angle=90).model_dump(by_alias=True),
|
||||
"tool": "/api/v1/general/rotate-pdf",
|
||||
"parameters": RotatePdfParams(angle=Angle(90)).model_dump(by_alias=True),
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ from stirling.contracts import (
|
||||
PdfTextSelection,
|
||||
ToolOperationStep,
|
||||
)
|
||||
from stirling.models.tool_models import OperationId, RotateParams
|
||||
from stirling.models.tool_models import Angle, RotatePdfParams, ToolEndpoint
|
||||
|
||||
|
||||
def test_orchestrator_request_accepts_user_message() -> None:
|
||||
@@ -38,8 +38,8 @@ def test_orchestrator_request_accepts_user_message() -> None:
|
||||
def test_agent_execution_request_uses_typed_agent_spec() -> None:
|
||||
steps: list[AgentSpecStep] = [
|
||||
ToolOperationStep(
|
||||
tool=OperationId.ROTATE,
|
||||
parameters=RotateParams(angle=90),
|
||||
tool=ToolEndpoint.ROTATE_PDF,
|
||||
parameters=RotatePdfParams(angle=Angle(90)),
|
||||
)
|
||||
]
|
||||
request = AgentExecutionRequest(
|
||||
@@ -57,13 +57,13 @@ def test_agent_execution_request_uses_typed_agent_spec() -> None:
|
||||
|
||||
|
||||
def test_edit_plan_response_has_typed_steps() -> None:
|
||||
steps = [ToolOperationStep(tool=OperationId.ROTATE, parameters=RotateParams(angle=90))]
|
||||
steps = [ToolOperationStep(tool=ToolEndpoint.ROTATE_PDF, parameters=RotatePdfParams(angle=Angle(90)))]
|
||||
response = EditPlanResponse(
|
||||
summary="Rotate the input PDF by 90 degrees.",
|
||||
steps=steps,
|
||||
)
|
||||
|
||||
assert response.steps[0].tool == OperationId.ROTATE
|
||||
assert response.steps[0].tool == ToolEndpoint.ROTATE_PDF
|
||||
|
||||
|
||||
def test_pdf_question_answer_defaults_evidence_list() -> None:
|
||||
|
||||
@@ -13,7 +13,7 @@ from stirling.contracts import (
|
||||
EditPlanResponse,
|
||||
ToolOperationStep,
|
||||
)
|
||||
from stirling.models.tool_models import CompressParams, OperationId, RotateParams
|
||||
from stirling.models.tool_models import Angle, FlattenParams, RotatePdfParams, ToolEndpoint
|
||||
from stirling.services.runtime import AppRuntime
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ class StubUserSpecAgent(UserSpecAgent):
|
||||
summary="Rotate the document.",
|
||||
steps=[
|
||||
ToolOperationStep(
|
||||
tool=OperationId.ROTATE,
|
||||
parameters=RotateParams(angle=90),
|
||||
tool=ToolEndpoint.ROTATE_PDF,
|
||||
parameters=RotatePdfParams(angle=Angle(90)),
|
||||
)
|
||||
],
|
||||
)
|
||||
@@ -63,8 +63,8 @@ async def test_user_spec_agent_drafts_agent_spec(runtime: AppRuntime) -> None:
|
||||
objective="Normalize invoices before accounting review.",
|
||||
steps=[
|
||||
ToolOperationStep(
|
||||
tool=OperationId.ROTATE,
|
||||
parameters=RotateParams(angle=90),
|
||||
tool=ToolEndpoint.ROTATE_PDF,
|
||||
parameters=RotatePdfParams(angle=Angle(90)),
|
||||
)
|
||||
],
|
||||
),
|
||||
@@ -98,8 +98,8 @@ async def test_user_spec_agent_revises_existing_draft(runtime: AppRuntime) -> No
|
||||
objective="Normalize invoices before accounting review.",
|
||||
steps=[
|
||||
ToolOperationStep(
|
||||
tool=OperationId.ROTATE,
|
||||
parameters=RotateParams(angle=90),
|
||||
tool=ToolEndpoint.ROTATE_PDF,
|
||||
parameters=RotatePdfParams(angle=Angle(90)),
|
||||
)
|
||||
],
|
||||
)
|
||||
@@ -112,12 +112,12 @@ async def test_user_spec_agent_revises_existing_draft(runtime: AppRuntime) -> No
|
||||
objective="Normalize invoices before accounting review.",
|
||||
steps=[
|
||||
ToolOperationStep(
|
||||
tool=OperationId.ROTATE,
|
||||
parameters=RotateParams(angle=90),
|
||||
tool=ToolEndpoint.ROTATE_PDF,
|
||||
parameters=RotatePdfParams(angle=Angle(90)),
|
||||
),
|
||||
ToolOperationStep(
|
||||
tool=OperationId.COMPRESS,
|
||||
parameters=CompressParams(compression_level=5),
|
||||
tool=ToolEndpoint.FLATTEN,
|
||||
parameters=FlattenParams(flatten_only_forms=False, render_dpi=None),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -138,8 +138,8 @@ async def test_user_spec_agent_revises_existing_draft(runtime: AppRuntime) -> No
|
||||
def test_tool_operation_step_rejects_mismatched_parameters() -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
ToolOperationStep(
|
||||
tool=OperationId.ROTATE,
|
||||
parameters=CompressParams(compression_level=5),
|
||||
tool=ToolEndpoint.ROTATE_PDF,
|
||||
parameters=FlattenParams(flatten_only_forms=False, render_dpi=None),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user