From a92722ff13315b84b3ef14948df556bdaeaa057c Mon Sep 17 00:00:00 2001 From: EthanHealy01 Date: Mon, 29 Jun 2026 22:10:09 +0100 Subject: [PATCH] remove classify as a user facing tool --- .../controller/api/ClassifyTagController.java | 8 +++--- .../agents/default_taxonomy.generated.json | 1 + .../stirling/agents/document_classifier.py | 8 +++++- engine/src/stirling/contracts/common.py | 1 - .../stirling/contracts/document_classifier.py | 8 +++--- .../tests/test_document_classifier_routes.py | 1 - .../public/locales/en-US/translation.toml | 4 --- frontend/editor/scripts/generate-taxonomy.mts | 10 ++++++- .../components/policies/usePolicyAutoRun.ts | 2 ++ .../tools/classify/useClassifyOperation.ts | 27 ------------------- .../proprietary/services/policyPipeline.ts | 21 ++++++++++++--- 11 files changed, 43 insertions(+), 48 deletions(-) delete mode 100644 frontend/editor/src/proprietary/hooks/tools/classify/useClassifyOperation.ts diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/ClassifyTagController.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/ClassifyTagController.java index 13adb03809..f7391505a1 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/ClassifyTagController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/ClassifyTagController.java @@ -1,7 +1,5 @@ package stirling.software.proprietary.controller.api; -import com.fasterxml.jackson.annotation.JsonInclude; - import java.io.IOException; import java.util.ArrayList; import java.util.LinkedHashSet; @@ -19,6 +17,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import com.fasterxml.jackson.annotation.JsonInclude; + import io.github.pixee.security.Filenames; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; @@ -152,8 +152,8 @@ public class ClassifyTagController { /** * Override point for a future per-org / DB-configured taxonomy: resolve the caller's taxonomy - * here and return it (engine shape) to classify against; {@code null} falls back to the engine's - * generated default. Always null today. + * here and return it (engine shape) to classify against; {@code null} falls back to the + * engine's generated default. Always null today. */ private JsonNode resolveTaxonomyOverride() { return null; diff --git a/engine/src/stirling/agents/default_taxonomy.generated.json b/engine/src/stirling/agents/default_taxonomy.generated.json index d44c2c40e5..4b8f07f92b 100644 --- a/engine/src/stirling/agents/default_taxonomy.generated.json +++ b/engine/src/stirling/agents/default_taxonomy.generated.json @@ -1,4 +1,5 @@ { + "_generated": "AUTO-GENERATED from frontend/editor/src/proprietary/data/classificationTaxonomy.ts by editor/scripts/generate-taxonomy.mts — do NOT edit by hand; run `task frontend:taxonomy`.", "categories": [ { "id": "invoice", diff --git a/engine/src/stirling/agents/document_classifier.py b/engine/src/stirling/agents/document_classifier.py index 28d9908573..014447159c 100644 --- a/engine/src/stirling/agents/document_classifier.py +++ b/engine/src/stirling/agents/document_classifier.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json import logging from pathlib import Path @@ -36,7 +37,12 @@ WINDOW_PAGES = 2 # `task frontend:taxonomy` — edit that file, not this JSON. Validated into the # typed contract on import, so a malformed entry fails fast. _DEFAULT_TAXONOMY_PATH = Path(__file__).with_name("default_taxonomy.generated.json") -DEFAULT_TAXONOMY = ClassificationTaxonomy.model_validate_json(_DEFAULT_TAXONOMY_PATH.read_text(encoding="utf-8")) +# The file carries an underscore-prefixed "_generated" notice (JSON has no +# comments); drop meta keys before validating against the strict contract. +_raw_taxonomy = json.loads(_DEFAULT_TAXONOMY_PATH.read_text(encoding="utf-8")) +DEFAULT_TAXONOMY = ClassificationTaxonomy.model_validate( + {key: value for key, value in _raw_taxonomy.items() if not key.startswith("_")} +) _SYSTEM_PROMPT = ( diff --git a/engine/src/stirling/contracts/common.py b/engine/src/stirling/contracts/common.py index 4fde61cab3..8f35c9ceb4 100644 --- a/engine/src/stirling/contracts/common.py +++ b/engine/src/stirling/contracts/common.py @@ -63,7 +63,6 @@ class WorkflowOutcome(StrEnum): UNSUPPORTED_CAPABILITY = "unsupported_capability" GENERATE_FILE = "generate_file" CONVERT_MARKDOWN = "convert_markdown" - CLASSIFICATION = "classification" class ArtifactKind(StrEnum): diff --git a/engine/src/stirling/contracts/document_classifier.py b/engine/src/stirling/contracts/document_classifier.py index eb55ccd78e..13598ae96b 100644 --- a/engine/src/stirling/contracts/document_classifier.py +++ b/engine/src/stirling/contracts/document_classifier.py @@ -1,12 +1,9 @@ from __future__ import annotations -from typing import Literal - from pydantic import Field from stirling.models import ApiModel -from .common import WorkflowOutcome from .documents import PageText @@ -55,10 +52,11 @@ class DocumentClassificationResponse(ApiModel): ``category`` and ``doc_type`` are ids drawn from the taxonomy, or the sentinel ``"unknown"`` when the model's answer fell outside it. ``tags`` are - the subset of the model's tags that exist in the taxonomy. + the subset of the model's tags that exist in the taxonomy. This is a plain + answer from a dedicated endpoint — it carries no ``outcome`` discriminator + (it isn't one of the orchestrator's WorkflowOutcome-routed union responses). """ - outcome: Literal[WorkflowOutcome.CLASSIFICATION] = WorkflowOutcome.CLASSIFICATION category: str doc_type: str type_confidence: float = Field(ge=0.0, le=1.0) diff --git a/engine/tests/test_document_classifier_routes.py b/engine/tests/test_document_classifier_routes.py index 4f1eebd884..ec5468f322 100644 --- a/engine/tests/test_document_classifier_routes.py +++ b/engine/tests/test_document_classifier_routes.py @@ -44,7 +44,6 @@ def test_classify_returns_camel_cased_result(classification_client: TestClient) ) assert response.status_code == 200 body = response.json() - assert body["outcome"] == "classification" assert body["category"] == "contract" assert body["docType"] == "nda" assert body["typeConfidence"] == 0.96 diff --git a/frontend/editor/public/locales/en-US/translation.toml b/frontend/editor/public/locales/en-US/translation.toml index 71586b517d..7901d4ee0b 100644 --- a/frontend/editor/public/locales/en-US/translation.toml +++ b/frontend/editor/public/locales/en-US/translation.toml @@ -4298,10 +4298,6 @@ desc = "Compares and shows the differences between 2 PDF Documents" tags = "difference,compare,diff,compare PDFs,compare documents,find differences,show differences,changes,what changed,track changes,revisions,version compare,side by side,contrast,delta" title = "Compare" -[home.classify] -desc = "Identify a document's type and tag its metadata." -title = "Classify" - [home.compress] desc = "Compress PDFs to reduce their file size." tags = "shrink,reduce,optimize,compress,smaller,downsize,file size,reduce size,minimize,make smaller,decrease size,optimize size" diff --git a/frontend/editor/scripts/generate-taxonomy.mts b/frontend/editor/scripts/generate-taxonomy.mts index abd0dc1c7e..511f1da94e 100644 --- a/frontend/editor/scripts/generate-taxonomy.mts +++ b/frontend/editor/scripts/generate-taxonomy.mts @@ -34,7 +34,15 @@ const outPath = resolve( "engine/src/stirling/agents/default_taxonomy.generated.json", ); -const json = JSON.stringify(DEFAULT_CLASSIFICATION_TAXONOMY, null, 2) + "\n"; +const NOTICE = + "AUTO-GENERATED from frontend/editor/src/proprietary/data/classificationTaxonomy.ts " + + "by editor/scripts/generate-taxonomy.mts — do NOT edit by hand; run `task frontend:taxonomy`."; +const json = + JSON.stringify( + { _generated: NOTICE, ...DEFAULT_CLASSIFICATION_TAXONOMY }, + null, + 2, + ) + "\n"; if (process.argv.includes("--check")) { const current = existsSync(outPath) ? readFileSync(outPath, "utf8") : ""; diff --git a/frontend/editor/src/proprietary/components/policies/usePolicyAutoRun.ts b/frontend/editor/src/proprietary/components/policies/usePolicyAutoRun.ts index 8440d53b5b..6c286646e2 100644 --- a/frontend/editor/src/proprietary/components/policies/usePolicyAutoRun.ts +++ b/frontend/editor/src/proprietary/components/policies/usePolicyAutoRun.ts @@ -489,6 +489,7 @@ export async function runPolicyOnFile( backendId: string, fileId: FileId, fileName: string, + replaceRunId?: string, ): Promise { // A freshly-uploaded file's bytes are written to IndexedDB asynchronously, so // its stub can appear in the file list a beat before getStirlingFile resolves @@ -516,6 +517,7 @@ export async function runPolicyOnFile( } try { const runId = await runStoredPolicy(backendId, [file]); + if (replaceRunId) removeRun(replaceRunId); // recordRunStart marks this (policy, file) dispatched as it records the run. recordRunStart({ runId, diff --git a/frontend/editor/src/proprietary/hooks/tools/classify/useClassifyOperation.ts b/frontend/editor/src/proprietary/hooks/tools/classify/useClassifyOperation.ts deleted file mode 100644 index 5774d30065..0000000000 --- a/frontend/editor/src/proprietary/hooks/tools/classify/useClassifyOperation.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ToolType } from "@app/hooks/tools/shared/useToolOperation"; - -/** Classify-and-tag takes no user parameters — it only needs the file. */ -export type ClassifyParameters = Record; - -export const defaultParameters: ClassifyParameters = {}; - -// Static function shared by the registry/automation executor. The backend reads -// only the file: it classifies via the AI engine and writes the result into the -// StirlingPDFClassification metadata field. -export const buildClassifyFormData = ( - _parameters: ClassifyParameters, - file: File, -): FormData => { - const formData = new FormData(); - formData.append("fileInput", file); - return formData; -}; - -export const classifyOperationConfig = { - toolType: ToolType.singleFile, - buildFormData: buildClassifyFormData, - operationType: "classify", - endpoint: "/api/v1/ai/tools/classify-and-tag", - multiFileEndpoint: false, - defaultParameters, -} as const; diff --git a/frontend/editor/src/proprietary/services/policyPipeline.ts b/frontend/editor/src/proprietary/services/policyPipeline.ts index 21e304e4fb..06294dfa44 100644 --- a/frontend/editor/src/proprietary/services/policyPipeline.ts +++ b/frontend/editor/src/proprietary/services/policyPipeline.ts @@ -95,6 +95,16 @@ export interface PolicyRunView { createdAt: number; } +/** + * Operations that run as policy pipeline steps but are NOT user-facing tools, so + * they have no tool-registry entry and never appear in the tool picker. Maps the + * operation id straight to its backend endpoint. + */ +const POLICY_OPERATION_ENDPOINTS: Record = { + // Document classification — dispatched only by the Classification policy. + classify: "/api/v1/ai/tools/classify-and-tag", +}; + /** Resolve a frontend operation id to its backend tool endpoint path. */ function resolveEndpoint( operation: string, @@ -103,10 +113,13 @@ function resolveEndpoint( ): string | null { const config = toolRegistry[operation as keyof ToolRegistry]?.operationConfig; const endpoint = config?.endpoint; - if (!endpoint) return null; - const resolved = - typeof endpoint === "function" ? endpoint(parameters) : endpoint; - return resolved ?? null; + if (endpoint) { + const resolved = + typeof endpoint === "function" ? endpoint(parameters) : endpoint; + if (resolved) return resolved; + } + // Policy-only operations have no registry entry; resolve them directly. + return POLICY_OPERATION_ENDPOINTS[operation] ?? null; } /**