Inform AI engine which endpoints are disabled on the backend (#6251)

# Description of Changes
Have the Java send a list of enabled endpoints to the AI engine so it
can intelligently respond to the user that the tool does exist but is
disabled on the server so it can't acutally run the operation, instead
of the current behaviour where it sends the API call back and then 503
errors because the execution fails when the URL is disabled.

<img width="380" height="208" alt="image"
src="https://github.com/user-attachments/assets/5842fb2e-2e55-45a5-8205-25515636daae"
/>

---------

Co-authored-by: EthanHealy01 <[email protected]>
This commit is contained in:
James Brunton
2026-05-01 14:59:53 +00:00
committed by GitHub
co-authored by EthanHealy01
parent 5541dd666c
commit 51f5345151
13 changed files with 418 additions and 95 deletions
+72 -11
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import logging
from collections.abc import Iterable
from typing import Literal, overload
from pydantic import Field
@@ -140,7 +141,6 @@ class PdfEditParameterSelector:
class PdfEditAgent:
def __init__(self, runtime: AppRuntime) -> None:
self.runtime = runtime
self.supported_operations = list(OPERATIONS)
self.parameter_selector = PdfEditParameterSelector(runtime)
async def orchestrate(self, request: OrchestratorRequest) -> PdfEditResponse:
@@ -156,6 +156,7 @@ class PdfEditAgent:
files=request.files,
conversation_history=request.conversation_history,
page_text=extracted_text.files if extracted_text is not None else [],
enabled_endpoints=request.enabled_endpoints,
)
)
@@ -165,19 +166,37 @@ class PdfEditAgent:
async def handle(self, request: PdfEditRequest, allow_need_content: bool = True) -> PdfEditResponse: ...
async def handle(self, request: PdfEditRequest, allow_need_content: bool = True) -> PdfEditResponse:
logger.info(
"[pdf-edit] handle: files=%s has_text=%s allow_need_content=%s msg=%r",
"[pdf-edit] handle: files=%s has_text=%s allow_need_content=%s enabled=%s msg=%r",
[file.name for file in request.files],
has_page_text(request.page_text),
allow_need_content,
request.enabled_endpoints,
request.user_message,
)
selection = await self._select_plan(request, allow_need_content=allow_need_content)
supported_operations = self._get_supported_operations(request)
unavailable_operations = self._get_unavailable_operations(supported_operations)
if not supported_operations:
return EditCannotDoResponse(reason="No PDF edit operations are available on this server.")
selection = await self._select_plan(
request, supported_operations, unavailable_operations, allow_need_content=allow_need_content
)
if isinstance(selection, EditClarificationRequest | EditCannotDoResponse):
logger.info("[pdf-edit] selection -> %s: %s", selection.outcome, Pretty(selection))
return selection
if isinstance(selection, NeedContentResponse):
logger.info("[pdf-edit] selection -> need_content: %s", selection.reason)
return self._fill_need_content_defaults(selection, request)
enabled = set(supported_operations)
unsupported = [op for op in selection.operations if op not in enabled]
if unsupported:
logger.warning("[pdf-edit] plan referenced unavailable operations: %s", [op.name for op in unsupported])
return EditCannotDoResponse(
reason=(
"The following operations are not available on this server "
"(either disabled by the administrator or not installed): "
+ ", ".join(op.name for op in unsupported)
)
)
logger.info("[pdf-edit] plan: %s", [op.name for op in selection.operations])
steps: list[ToolOperationStep] = []
for operation_index, operation_id in enumerate(selection.operations):
@@ -202,18 +221,39 @@ class PdfEditAgent:
async def _select_plan(
self,
request: PdfEditRequest,
supported_operations: Iterable[ToolEndpoint],
unavailable_operations: Iterable[ToolEndpoint],
*,
allow_need_content: bool = True,
) -> PdfEditPlanOutput:
can_request_content = allow_need_content and not has_page_text(request.page_text)
agent = self._build_selection_agent(allow_need_content=can_request_content)
return await agent.select(self._build_selection_prompt(request))
agent = self._build_selection_agent(
supported_operations, unavailable_operations, allow_need_content=can_request_content
)
return await agent.select(self._build_selection_prompt(request, supported_operations, unavailable_operations))
def _build_selection_agent(self, *, allow_need_content: bool) -> PdfEditSelectionAgent:
def _build_selection_agent(
self,
supported_operations: Iterable[ToolEndpoint],
unavailable_operations: Iterable[ToolEndpoint],
*,
allow_need_content: bool,
) -> PdfEditSelectionAgent:
unavailable_clause = (
f" The following operations exist on this server but are NOT currently available "
f"(disabled by the administrator or not installed in this build) and must NOT appear "
f"in any plan: {self._get_operations_prompt(unavailable_operations)}. "
"If the user asks for one of these, return cannot_do, name the operation, and explain "
"that it exists but isn't available on this server."
if unavailable_operations
else ""
)
return PdfEditSelectionAgent(
self.runtime,
base_system_prompt=(
"Plan PDF edit requests. "
f"Supported operations are: {self._supported_operations_prompt()}. "
f"Supported operations are: {self._get_operations_prompt(supported_operations)}."
f"{unavailable_clause} "
"Return an ordered list of one or more supported operations for the plan. "
"Do not produce operation parameters in this stage. "
"Return need_clarification when the request is genuinely ambiguous. "
@@ -224,17 +264,38 @@ class PdfEditAgent:
allow_need_content=allow_need_content,
)
def _build_selection_prompt(self, request: PdfEditRequest) -> str:
def _build_selection_prompt(
self,
request: PdfEditRequest,
supported_operations: Iterable[ToolEndpoint],
unavailable_operations: Iterable[ToolEndpoint],
) -> str:
unavailable_line = (
"Unavailable operations (exist but not currently usable): "
f"{self._get_operations_prompt(unavailable_operations)}\n"
if unavailable_operations
else ""
)
return (
f"Conversation history:\n{format_conversation_history(request.conversation_history)}\n"
f"User request: {request.user_message}\n"
f"Files: {format_file_names(request.files)}\n"
f"Supported operations: {self._supported_operations_prompt()}\n"
f"Supported operations: {self._get_operations_prompt(supported_operations)}\n"
f"{unavailable_line}"
f"Extracted page text:\n{format_page_text(request.page_text)}"
)
def _supported_operations_prompt(self) -> str:
return ", ".join(f"{op.name} ({op.value})" for op in self.supported_operations)
def _get_supported_operations(self, request: PdfEditRequest) -> Iterable[ToolEndpoint]:
return request.enabled_endpoints
@staticmethod
def _get_unavailable_operations(supported_operations: Iterable[ToolEndpoint]) -> Iterable[ToolEndpoint]:
supported_set = set(supported_operations)
return [op for op in OPERATIONS if op not in supported_set]
@staticmethod
def _get_operations_prompt(operations: Iterable[ToolEndpoint]) -> str:
return ", ".join(f"{op.name} ({op.value})" for op in operations)
def _fill_need_content_defaults(
self,