Skip to content

Composer & Intent Detection

The Composer is Juca’s input component — a pill-shaped text field where users type legal queries. Behind it, the orchestration layer detects intent, fills required slots, and routes queries to specialized tools that call the Valter API.

The Composer lives in src/components/canvas/Composer.tsx and follows the Liquid Legal design language:

  • Shape: Pill-shaped container with white background and subtle shadow
  • Input: Full-width text field with placeholder
  • Send button: Circular, dark (--ink-primary), with arrow icon
  • Behavior: Submits via Server Action, streams results via SSE

On submit, the Composer calls a Server Action that starts the orchestration pipeline.

The orchestration pipeline lives in src/lib/unified/ and processes queries in four stages:

User input
→ Intent Detector (classify query)
→ Slot Filling (extract parameters)
→ Clarification Policy (ask if info is missing)
→ Tool Registry (execute the right tool)

The Intent Detector (src/lib/unified/) classifies user queries into categories that map to tools. It considers the query text, conversation context, and any referenced artifacts.

Each tool declares required parameters (slots). The slot filler extracts values from the user’s query — for example, legal area, court, case number, or topic.

If the intent is ambiguous or required slots are empty, the Clarification Policy determines what to ask. This renders as action_prompt blocks in the WorkCanvas, presenting options to the user.

The Tool Registry maps intents to tool implementations. Each tool is a class extending BaseTool:

ToolIDPriorityCapabilitiesKey Artifacts
AnalyzerToolanalyzer9 (highest)case_analysis, search, conversation, follow_upcase_analysis, document
JurisTooljuris8search, follow_upsearch_results, document
RatioToolratio7extraction, follow_upirac_extraction
CompareToolcompare5comparisonmodel_comparison
InsightsToolinsights4statisticsinsights_stats

Priority determines which tool handles a query when multiple tools match. The AnalyzerTool (priority 9) handles complex case analysis scenarios; JurisTool (priority 8) handles direct search queries.

All tools implement this interface:

interface BaseTool {
id: string;
priority: number;
capabilities: ToolCapability[];
producesArtifacts: string[];
consumesArtifacts?: string[];
execute(params: ToolParams, onProgress?: ProgressCallback): Promise<ToolResult>;
}
type ToolCapability = 'search' | 'extraction' | 'comparison'
| 'statistics' | 'conversation' | 'follow_up' | 'case_analysis';

The JurisTool uses a hybrid search strategy with configurable weights:

{
strategy: 'hybrid',
weights: { bm25: 0.6, semantic: 0.4, kg: 1.0 },
defaultLimit: 10
}

Real-time progress is streamed to the client via Server-Sent Events through /api/v2/stream:

Request: POST { sessionId, text }

Event types:

EventDataPurpose
stage{ stage, label, tool? }Progress update (e.g., “Searching precedents…”)
result{ blocks: Block[] }Final blocks to render
error{ message }Error information

Client hook: useSSEStream() returns { sendStreaming, isStreaming, currentStage, abort } — providing real-time UI feedback and abort capability.