@jayarrowz/mcp-arsr 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/Dockerfile +19 -0
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +325 -0
- package/dist/src/schemas/tools.d.ts +85 -0
- package/dist/src/schemas/tools.js +100 -0
- package/dist/src/services/llm.d.ts +56 -0
- package/dist/src/services/llm.js +361 -0
- package/dist/src/types.d.ts +53 -0
- package/dist/src/types.js +7 -0
- package/glama.json +6 -0
- package/package.json +31 -0
- package/smithery.yaml +13 -0
- package/src/index.ts +395 -0
- package/src/schemas/tools.ts +118 -0
- package/src/services/llm.ts +480 -0
- package/src/types.ts +67 -0
- package/tsconfig.json +16 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
+
import express from "express";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
DraftInputSchema,
|
|
8
|
+
DecomposeInputSchema,
|
|
9
|
+
ScoreInputSchema,
|
|
10
|
+
RetrieveInputSchema,
|
|
11
|
+
ReviseInputSchema,
|
|
12
|
+
ContinueInputSchema,
|
|
13
|
+
} from "./schemas/tools.js";
|
|
14
|
+
import type {
|
|
15
|
+
DraftInput,
|
|
16
|
+
DecomposeInput,
|
|
17
|
+
ScoreInput,
|
|
18
|
+
RetrieveInput,
|
|
19
|
+
ReviseInput,
|
|
20
|
+
ContinueInput,
|
|
21
|
+
} from "./schemas/tools.js";
|
|
22
|
+
import {
|
|
23
|
+
generateDraft,
|
|
24
|
+
decomposeClaims,
|
|
25
|
+
scoreClaims,
|
|
26
|
+
retrieveEvidence,
|
|
27
|
+
reviseDraft,
|
|
28
|
+
shouldContinue,
|
|
29
|
+
} from "./services/llm.js";
|
|
30
|
+
import { DEFAULT_CONFIG } from "./types.js";
|
|
31
|
+
|
|
32
|
+
const server = new McpServer({
|
|
33
|
+
name: "arsr-mcp-server",
|
|
34
|
+
version: "0.1.0",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
server.registerTool(
|
|
38
|
+
"arsr_draft_response",
|
|
39
|
+
{
|
|
40
|
+
title: "Draft Response",
|
|
41
|
+
description: `Generate an initial candidate response to a user query. This is the first step in the ARSR refinement loop.
|
|
42
|
+
|
|
43
|
+
The draft is generated by an inner LLM and may contain inaccuracies — that's expected. Subsequent tools (decompose, score, retrieve, revise) will iteratively correct it.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
- query (string): The user's question to answer
|
|
47
|
+
- context (string, optional): Additional context or constraints
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
{ "draft": "The generated response text", "is_refusal": false }`,
|
|
51
|
+
inputSchema: DraftInputSchema,
|
|
52
|
+
annotations: {
|
|
53
|
+
readOnlyHint: true,
|
|
54
|
+
destructiveHint: false,
|
|
55
|
+
idempotentHint: false,
|
|
56
|
+
openWorldHint: true,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
async (params: DraftInput) => {
|
|
60
|
+
try {
|
|
61
|
+
const { draft, is_refusal } = await generateDraft(params.query, params.context);
|
|
62
|
+
const output = { draft, is_refusal };
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
65
|
+
structuredContent: output,
|
|
66
|
+
};
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return {
|
|
69
|
+
isError: true,
|
|
70
|
+
content: [{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `Error generating draft: ${error instanceof Error ? error.message : String(error)}. Ensure ANTHROPIC_API_KEY is set.`,
|
|
73
|
+
}],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
server.registerTool(
|
|
80
|
+
"arsr_decompose_claims",
|
|
81
|
+
{
|
|
82
|
+
title: "Decompose Claims",
|
|
83
|
+
description: `Split a draft response into individually verifiable atomic claims.
|
|
84
|
+
|
|
85
|
+
Each claim is a single factual statement that can be independently fact-checked. Opinions, hedges, and meta-commentary are excluded.
|
|
86
|
+
|
|
87
|
+
If is_refusal is true (from arsr_draft_response output) and original_query is provided, claims will be extracted from the query instead of the draft.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
- draft (string): The response text to decompose
|
|
91
|
+
- original_query (string, optional): The user's original question, used as fallback if draft is a refusal
|
|
92
|
+
- is_refusal (boolean, optional): Whether the draft was classified as a refusal by arsr_draft_response
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
{ "claims": [{ "id": "c1", "text": "The claim as a statement", "source_span": "exact quote from draft" }] }`,
|
|
96
|
+
inputSchema: DecomposeInputSchema,
|
|
97
|
+
annotations: {
|
|
98
|
+
readOnlyHint: true,
|
|
99
|
+
destructiveHint: false,
|
|
100
|
+
idempotentHint: true,
|
|
101
|
+
openWorldHint: false,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
async (params: DecomposeInput) => {
|
|
105
|
+
try {
|
|
106
|
+
const claims = await decomposeClaims(params.draft, params.original_query, params.is_refusal ?? false);
|
|
107
|
+
const output = { claims, count: claims.length };
|
|
108
|
+
return {
|
|
109
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
110
|
+
structuredContent: output,
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
isError: true,
|
|
115
|
+
content: [{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: `Error decomposing claims: ${error instanceof Error ? error.message : String(error)}`,
|
|
118
|
+
}],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
server.registerTool(
|
|
125
|
+
"arsr_score_uncertainty",
|
|
126
|
+
{
|
|
127
|
+
title: "Score Claim Uncertainty",
|
|
128
|
+
description: `Estimate confidence for each claim using semantic entropy and consistency analysis.
|
|
129
|
+
|
|
130
|
+
Each claim receives a confidence score (0-1) and entropy score (0-1). Low confidence / high entropy indicates the claim should be fact-checked.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
- claims (array): Claims to score, each with { id, text, source_span }
|
|
134
|
+
- n_samples (number, optional): Number of rephrasings for entropy (default: 3)
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
{ "scored": [{ "id": "c1", "text": "...", "confidence": 0.92, "entropy": 0.15, "method": "semantic_entropy" }] }
|
|
138
|
+
|
|
139
|
+
Use the confidence_threshold (default 0.85) to filter which claims need evidence retrieval.`,
|
|
140
|
+
inputSchema: ScoreInputSchema,
|
|
141
|
+
annotations: {
|
|
142
|
+
readOnlyHint: true,
|
|
143
|
+
destructiveHint: false,
|
|
144
|
+
idempotentHint: false, // Stochastic
|
|
145
|
+
openWorldHint: false,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
async (params: ScoreInput) => {
|
|
149
|
+
try {
|
|
150
|
+
const scored = await scoreClaims(params.claims);
|
|
151
|
+
const avgConfidence = scored.reduce((s, c) => s + c.confidence, 0) / scored.length;
|
|
152
|
+
const lowConfCount = scored.filter((c) => c.confidence < DEFAULT_CONFIG.confidence_threshold).length;
|
|
153
|
+
|
|
154
|
+
const output = {
|
|
155
|
+
scored,
|
|
156
|
+
summary: {
|
|
157
|
+
total_claims: scored.length,
|
|
158
|
+
avg_confidence: Math.round(avgConfidence * 1000) / 1000,
|
|
159
|
+
low_confidence_count: lowConfCount,
|
|
160
|
+
threshold: DEFAULT_CONFIG.confidence_threshold,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
165
|
+
structuredContent: output,
|
|
166
|
+
};
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return {
|
|
169
|
+
isError: true,
|
|
170
|
+
content: [{
|
|
171
|
+
type: "text",
|
|
172
|
+
text: `Error scoring claims: ${error instanceof Error ? error.message : String(error)}`,
|
|
173
|
+
}],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
server.registerTool(
|
|
180
|
+
"arsr_retrieve_evidence",
|
|
181
|
+
{
|
|
182
|
+
title: "Retrieve Evidence",
|
|
183
|
+
description: `Fetch evidence for low-confidence claims using uncertainty-guided retrieval.
|
|
184
|
+
|
|
185
|
+
For each claim, the inner LLM generates smart search queries (adversarial by default — designed to DISPROVE the claim), executes web searches, and evaluates each result's stance (supports/contradicts/neutral).
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
- claims_to_check (array): Low-confidence ScoredClaims to investigate
|
|
189
|
+
- strategy (string, optional): "adversarial" (default), "confirmatory", or "balanced"
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
{ "evidence": [{ "claim_id": "c1", "docs": [...], "overall_stance": "contradicted", "summary": "..." }] }
|
|
193
|
+
|
|
194
|
+
IMPORTANT: Only pass claims with confidence BELOW the threshold. Do not waste budget on high-confidence claims.`,
|
|
195
|
+
inputSchema: RetrieveInputSchema,
|
|
196
|
+
annotations: {
|
|
197
|
+
readOnlyHint: true,
|
|
198
|
+
destructiveHint: false,
|
|
199
|
+
idempotentHint: false,
|
|
200
|
+
openWorldHint: true,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
async (params: RetrieveInput) => {
|
|
204
|
+
try {
|
|
205
|
+
const evidence = await retrieveEvidence(
|
|
206
|
+
params.claims_to_check.map((c) => ({
|
|
207
|
+
...c,
|
|
208
|
+
method: "semantic_entropy" as const,
|
|
209
|
+
})),
|
|
210
|
+
params.strategy ?? "adversarial"
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const supported = evidence.filter((e) => e.overall_stance === "supported").length;
|
|
214
|
+
const contradicted = evidence.filter((e) => e.overall_stance === "contradicted").length;
|
|
215
|
+
const mixed = evidence.filter((e) => e.overall_stance === "mixed").length;
|
|
216
|
+
|
|
217
|
+
const output = {
|
|
218
|
+
evidence,
|
|
219
|
+
summary: {
|
|
220
|
+
claims_checked: evidence.length,
|
|
221
|
+
supported,
|
|
222
|
+
contradicted,
|
|
223
|
+
mixed,
|
|
224
|
+
insufficient: evidence.length - supported - contradicted - mixed,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
|
|
229
|
+
structuredContent: output,
|
|
230
|
+
};
|
|
231
|
+
} catch (error) {
|
|
232
|
+
return {
|
|
233
|
+
isError: true,
|
|
234
|
+
content: [{
|
|
235
|
+
type: "text",
|
|
236
|
+
text: `Error retrieving evidence: ${error instanceof Error ? error.message : String(error)}`,
|
|
237
|
+
}],
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
server.registerTool(
|
|
244
|
+
"arsr_revise_response",
|
|
245
|
+
{
|
|
246
|
+
title: "Revise Response",
|
|
247
|
+
description: `Rewrite the draft integrating evidence findings. Corrects contradicted claims, hedges mixed claims, and flags irreconcilable conflicts.
|
|
248
|
+
|
|
249
|
+
IMPORTANT: If the draft was a refusal/non-answer (is_refusal from arsr_draft_response), pass original_query and is_refusal. The revision engine will then generate a COMPLETELY NEW answer from the evidence instead of trying to edit a non-answer.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
- draft (string): The current draft to revise
|
|
253
|
+
- evidence (array): Evidence from retrieve_evidence
|
|
254
|
+
- scored (array): Scored claims from score_uncertainty
|
|
255
|
+
- original_query (string, optional): The user's original question, critical if draft was a refusal
|
|
256
|
+
- is_refusal (boolean, optional): Whether the draft was classified as a refusal by arsr_draft_response
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
{
|
|
260
|
+
"revised": "The corrected response text",
|
|
261
|
+
"changes": [{ "claim_id": "c1", "action": "corrected", "original": "...", "revised": "...", "reason": "..." }],
|
|
262
|
+
"conflicts": [{ "claim_id": "c2", "description": "Sources disagree about..." }]
|
|
263
|
+
}`,
|
|
264
|
+
inputSchema: ReviseInputSchema,
|
|
265
|
+
annotations: {
|
|
266
|
+
readOnlyHint: true,
|
|
267
|
+
destructiveHint: false,
|
|
268
|
+
idempotentHint: false,
|
|
269
|
+
openWorldHint: false,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
async (params: ReviseInput) => {
|
|
273
|
+
try {
|
|
274
|
+
const result = await reviseDraft(
|
|
275
|
+
params.draft,
|
|
276
|
+
params.evidence,
|
|
277
|
+
params.scored.map((s) => ({
|
|
278
|
+
...s,
|
|
279
|
+
method: "semantic_entropy" as const,
|
|
280
|
+
})),
|
|
281
|
+
params.original_query,
|
|
282
|
+
params.is_refusal ?? false
|
|
283
|
+
);
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
286
|
+
structuredContent: result,
|
|
287
|
+
};
|
|
288
|
+
} catch (error) {
|
|
289
|
+
return {
|
|
290
|
+
isError: true,
|
|
291
|
+
content: [{
|
|
292
|
+
type: "text",
|
|
293
|
+
text: `Error revising response: ${error instanceof Error ? error.message : String(error)}`,
|
|
294
|
+
}],
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
server.registerTool(
|
|
301
|
+
"arsr_should_continue",
|
|
302
|
+
{
|
|
303
|
+
title: "Should Continue",
|
|
304
|
+
description: `Decide whether to run another refinement iteration or finalize the response.
|
|
305
|
+
|
|
306
|
+
Uses three stopping criteria:
|
|
307
|
+
1. Budget: stop if iteration >= max_iterations
|
|
308
|
+
2. Threshold: stop if ALL claims exceed confidence_threshold
|
|
309
|
+
3. Convergence: stop if average confidence didn't improve by ≥0.02
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
- iteration (number): Current iteration (1-based)
|
|
313
|
+
- scored (array): Current confidence scores [{ id, confidence, entropy }]
|
|
314
|
+
- budget (number, optional): Max iterations (default: 3)
|
|
315
|
+
- confidence_threshold (number, optional): Target confidence (default: 0.85)
|
|
316
|
+
- previous_avg_confidence (number | null, optional): Prior iteration's avg confidence
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
{ "decision": "continue" | "stop", "reason": "Explanation of why" }`,
|
|
320
|
+
inputSchema: ContinueInputSchema,
|
|
321
|
+
annotations: {
|
|
322
|
+
readOnlyHint: true,
|
|
323
|
+
destructiveHint: false,
|
|
324
|
+
idempotentHint: true,
|
|
325
|
+
openWorldHint: false,
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
async (params: ContinueInput) => {
|
|
329
|
+
try {
|
|
330
|
+
const result = await shouldContinue(
|
|
331
|
+
params.iteration,
|
|
332
|
+
params.scored.map((s) => ({
|
|
333
|
+
...s,
|
|
334
|
+
text: "",
|
|
335
|
+
source_span: "",
|
|
336
|
+
method: "semantic_entropy" as const,
|
|
337
|
+
})),
|
|
338
|
+
params.budget ?? DEFAULT_CONFIG.max_iterations,
|
|
339
|
+
params.confidence_threshold ?? DEFAULT_CONFIG.confidence_threshold,
|
|
340
|
+
params.previous_avg_confidence ?? null
|
|
341
|
+
);
|
|
342
|
+
return {
|
|
343
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
344
|
+
structuredContent: result,
|
|
345
|
+
};
|
|
346
|
+
} catch (error) {
|
|
347
|
+
return {
|
|
348
|
+
isError: true,
|
|
349
|
+
content: [{
|
|
350
|
+
type: "text",
|
|
351
|
+
text: `Error in continue decision: ${error instanceof Error ? error.message : String(error)}`,
|
|
352
|
+
}],
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
async function runStdio(): Promise<void> {
|
|
359
|
+
const transport = new StdioServerTransport();
|
|
360
|
+
await server.connect(transport);
|
|
361
|
+
console.error("ARSR MCP Server running on stdio");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function runHTTP(): Promise<void> {
|
|
365
|
+
const app = express();
|
|
366
|
+
app.use(express.json());
|
|
367
|
+
|
|
368
|
+
app.post("/mcp", async (req, res) => {
|
|
369
|
+
const transport = new StreamableHTTPServerTransport({
|
|
370
|
+
sessionIdGenerator: undefined,
|
|
371
|
+
enableJsonResponse: true,
|
|
372
|
+
});
|
|
373
|
+
res.on("close", () => transport.close());
|
|
374
|
+
await server.connect(transport);
|
|
375
|
+
await transport.handleRequest(req, res, req.body);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const port = parseInt(process.env.PORT || "3001");
|
|
379
|
+
app.listen(port, () => {
|
|
380
|
+
console.error(`ARSR MCP Server running on http://localhost:${port}/mcp`);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const transport = process.env.TRANSPORT || "stdio";
|
|
385
|
+
if (transport === "http") {
|
|
386
|
+
runHTTP().catch((error) => {
|
|
387
|
+
console.error("Server error:", error);
|
|
388
|
+
process.exit(1);
|
|
389
|
+
});
|
|
390
|
+
} else {
|
|
391
|
+
runStdio().catch((error) => {
|
|
392
|
+
console.error("Server error:", error);
|
|
393
|
+
process.exit(1);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const DraftInputSchema = z.object({
|
|
4
|
+
query: z.string()
|
|
5
|
+
.min(1, "Query must not be empty")
|
|
6
|
+
.describe("The user's question or request to answer"),
|
|
7
|
+
context: z.string()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Optional additional context, prior conversation, or domain constraints"),
|
|
10
|
+
}).strict();
|
|
11
|
+
|
|
12
|
+
export type DraftInput = z.infer<typeof DraftInputSchema>;
|
|
13
|
+
|
|
14
|
+
export const DecomposeInputSchema = z.object({
|
|
15
|
+
draft: z.string()
|
|
16
|
+
.min(1, "Draft must not be empty")
|
|
17
|
+
.describe("The candidate response text to decompose into atomic claims"),
|
|
18
|
+
original_query: z.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("The user's original question. If the draft is a refusal/non-answer, claims will be extracted from this query instead."),
|
|
21
|
+
is_refusal: z.boolean()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Whether the draft was classified as a refusal/non-answer by arsr_draft_response. If true (and original_query is provided), claims are extracted from the query instead of the draft."),
|
|
24
|
+
}).strict();
|
|
25
|
+
|
|
26
|
+
export type DecomposeInput = z.infer<typeof DecomposeInputSchema>;
|
|
27
|
+
|
|
28
|
+
export const ScoreInputSchema = z.object({
|
|
29
|
+
claims: z.array(z.object({
|
|
30
|
+
id: z.string().describe("Claim identifier (e.g. 'c1')"),
|
|
31
|
+
text: z.string().describe("The claim as a standalone factual statement"),
|
|
32
|
+
source_span: z.string().describe("The exact substring from the original draft"),
|
|
33
|
+
})).min(1, "At least one claim required")
|
|
34
|
+
.describe("Array of atomic claims to score"),
|
|
35
|
+
n_samples: z.number()
|
|
36
|
+
.int().min(1).max(10).default(3)
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Number of rephrasings for semantic entropy (default: 3)"),
|
|
39
|
+
}).strict();
|
|
40
|
+
|
|
41
|
+
export type ScoreInput = z.infer<typeof ScoreInputSchema>;
|
|
42
|
+
|
|
43
|
+
export const RetrieveInputSchema = z.object({
|
|
44
|
+
claims_to_check: z.array(z.object({
|
|
45
|
+
id: z.string(),
|
|
46
|
+
text: z.string(),
|
|
47
|
+
source_span: z.string(),
|
|
48
|
+
confidence: z.number(),
|
|
49
|
+
entropy: z.number(),
|
|
50
|
+
method: z.string(),
|
|
51
|
+
})).min(1, "At least one claim required")
|
|
52
|
+
.describe("Low-confidence claims to gather evidence for"),
|
|
53
|
+
strategy: z.enum(["adversarial", "confirmatory", "balanced"])
|
|
54
|
+
.default("adversarial")
|
|
55
|
+
.optional()
|
|
56
|
+
.describe("Retrieval strategy: 'adversarial' generates counter-queries, 'confirmatory' seeks support, 'balanced' does both"),
|
|
57
|
+
}).strict();
|
|
58
|
+
|
|
59
|
+
export type RetrieveInput = z.infer<typeof RetrieveInputSchema>;
|
|
60
|
+
|
|
61
|
+
export const ReviseInputSchema = z.object({
|
|
62
|
+
draft: z.string()
|
|
63
|
+
.min(1)
|
|
64
|
+
.describe("The current draft text to revise"),
|
|
65
|
+
evidence: z.array(z.object({
|
|
66
|
+
claim_id: z.string(),
|
|
67
|
+
claim_text: z.string(),
|
|
68
|
+
docs: z.array(z.object({
|
|
69
|
+
title: z.string(),
|
|
70
|
+
url: z.string(),
|
|
71
|
+
snippet: z.string(),
|
|
72
|
+
stance: z.enum(["supports", "contradicts", "neutral", "unclear"]),
|
|
73
|
+
})),
|
|
74
|
+
overall_stance: z.enum(["supported", "contradicted", "mixed", "insufficient"]),
|
|
75
|
+
summary: z.string(),
|
|
76
|
+
})).describe("Evidence gathered for each claim"),
|
|
77
|
+
scored: z.array(z.object({
|
|
78
|
+
id: z.string(),
|
|
79
|
+
text: z.string(),
|
|
80
|
+
source_span: z.string(),
|
|
81
|
+
confidence: z.number(),
|
|
82
|
+
entropy: z.number(),
|
|
83
|
+
method: z.string(),
|
|
84
|
+
})).describe("The scored claims from the uncertainty step"),
|
|
85
|
+
original_query: z.string()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("The user's original question. If the draft was a refusal, this is used to generate a new answer from evidence."),
|
|
88
|
+
is_refusal: z.boolean()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe("Whether the draft was classified as a refusal/non-answer by arsr_draft_response. If true, a new response is generated from evidence instead of revising the refusal."),
|
|
91
|
+
}).strict();
|
|
92
|
+
|
|
93
|
+
export type ReviseInput = z.infer<typeof ReviseInputSchema>;
|
|
94
|
+
|
|
95
|
+
export const ContinueInputSchema = z.object({
|
|
96
|
+
iteration: z.number()
|
|
97
|
+
.int().min(1)
|
|
98
|
+
.describe("Current iteration number (1-based)"),
|
|
99
|
+
scored: z.array(z.object({
|
|
100
|
+
id: z.string(),
|
|
101
|
+
confidence: z.number(),
|
|
102
|
+
entropy: z.number(),
|
|
103
|
+
})).describe("Current confidence scores for all claims"),
|
|
104
|
+
budget: z.number()
|
|
105
|
+
.int().min(1).max(10).default(3)
|
|
106
|
+
.optional()
|
|
107
|
+
.describe("Maximum iterations allowed (default: 3)"),
|
|
108
|
+
confidence_threshold: z.number()
|
|
109
|
+
.min(0).max(1).default(0.85)
|
|
110
|
+
.optional()
|
|
111
|
+
.describe("Stop when all claims exceed this confidence (default: 0.85)"),
|
|
112
|
+
previous_avg_confidence: z.number()
|
|
113
|
+
.nullable().default(null)
|
|
114
|
+
.optional()
|
|
115
|
+
.describe("Average confidence from previous iteration for convergence detection"),
|
|
116
|
+
}).strict();
|
|
117
|
+
|
|
118
|
+
export type ContinueInput = z.infer<typeof ContinueInputSchema>;
|