@ema.co/mcp-toolkit 2026.4.9-1 → 2026.4.9-3
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/dist/knowledge/pipeline/confidence.js +13 -0
- package/dist/knowledge/pipeline/document.js +31 -2
- package/dist/mcp/guidance.js +1 -1
- package/dist/mcp/handlers/feedback/index.js +12 -31
- package/dist/mcp/handlers/feedback/store.js +23 -9
- package/dist/mcp/handlers/knowledge/outcome-feedback.js +12 -23
- package/dist/mcp/handlers/knowledge/user-events.js +112 -0
- package/dist/mcp/knowledge-guidance-topics.js +15 -6
- package/dist/mcp/tools.js +4 -3
- package/package.json +1 -1
|
@@ -85,6 +85,19 @@ export function scoreToLabel(score) {
|
|
|
85
85
|
return "inferred";
|
|
86
86
|
return "low-confidence";
|
|
87
87
|
}
|
|
88
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
89
|
+
// Fragment helpers — GitHub-style #fragment on knowledge_ref
|
|
90
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
91
|
+
/** Strip #fragment from a knowledge_ref, returning the bare document ID. */
|
|
92
|
+
export function stripFragment(ref) {
|
|
93
|
+
const idx = ref.indexOf("#");
|
|
94
|
+
return idx === -1 ? ref : ref.slice(0, idx);
|
|
95
|
+
}
|
|
96
|
+
/** Extract the #fragment from a knowledge_ref, or undefined if none. */
|
|
97
|
+
export function extractFragment(ref) {
|
|
98
|
+
const idx = ref.indexOf("#");
|
|
99
|
+
return idx === -1 ? undefined : ref.slice(idx + 1);
|
|
100
|
+
}
|
|
88
101
|
/**
|
|
89
102
|
* Compute confidence adjustment based on the ratio of negative to total feedback.
|
|
90
103
|
*
|
|
@@ -34,20 +34,30 @@ export function toDeDocument(doc) {
|
|
|
34
34
|
const textContent = doc.structData?.content
|
|
35
35
|
|| [doc.structData?.name, doc.structData?.summary, doc.structData?.description].filter(Boolean).join("\n");
|
|
36
36
|
const contentHash = computeContentHash(textContent || doc.id);
|
|
37
|
+
const now = new Date().toISOString();
|
|
37
38
|
const augmented = {
|
|
38
39
|
...doc.structData,
|
|
39
40
|
content_hash: contentHash,
|
|
40
|
-
augmented_at:
|
|
41
|
+
augmented_at: now,
|
|
41
42
|
status: doc.structData?.status ?? "active",
|
|
42
43
|
confidence_score: doc.structData?.confidence_score
|
|
43
44
|
?? computeConfidenceScore(doc.structData?.provenance ?? "inferred"),
|
|
45
|
+
// Lifecycle timestamps — set once at publish, preserved on re-publish
|
|
46
|
+
// Aligned with knowledge-service's 3-layer model (produced_at/published_at)
|
|
47
|
+
published_at: doc.structData?.published_at ?? now,
|
|
48
|
+
produced_at: doc.structData?.produced_at ?? now,
|
|
44
49
|
};
|
|
45
|
-
// Compute freshness_tier
|
|
50
|
+
// Compute freshness_tier: TTL-based (explicit) or content-age (universal)
|
|
46
51
|
if (doc.structData?.ttl) {
|
|
47
52
|
const tier = computeFreshnessTier(doc.structData.ttl, doc.structData.extracted_at);
|
|
48
53
|
if (tier)
|
|
49
54
|
augmented.freshness_tier = tier;
|
|
50
55
|
}
|
|
56
|
+
else {
|
|
57
|
+
const tier = computeContentFreshnessTier(augmented.produced_at);
|
|
58
|
+
if (tier)
|
|
59
|
+
augmented.freshness_tier = tier;
|
|
60
|
+
}
|
|
51
61
|
augmentByType(doc, augmented);
|
|
52
62
|
return {
|
|
53
63
|
id: sanitizeId(doc.id),
|
|
@@ -58,6 +68,25 @@ export function toDeDocument(doc) {
|
|
|
58
68
|
},
|
|
59
69
|
};
|
|
60
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Universal content-age freshness tier (no TTL config required).
|
|
73
|
+
* Applies to all docs via produced_at — DE's boost-fresh/demote-expired
|
|
74
|
+
* controls handle the ranking impact.
|
|
75
|
+
*/
|
|
76
|
+
// TODO(knowledge-service): migrate alongside computeFreshnessTier when shared lib is ready
|
|
77
|
+
export function computeContentFreshnessTier(producedAt) {
|
|
78
|
+
if (!producedAt)
|
|
79
|
+
return undefined;
|
|
80
|
+
const ageMs = Date.now() - new Date(producedAt).getTime();
|
|
81
|
+
if (Number.isNaN(ageMs))
|
|
82
|
+
return undefined;
|
|
83
|
+
const ageDays = ageMs / (86_400_000);
|
|
84
|
+
if (ageDays < 7)
|
|
85
|
+
return "fresh";
|
|
86
|
+
if (ageDays >= 90)
|
|
87
|
+
return "stale";
|
|
88
|
+
return undefined; // neutral — no boost or penalty
|
|
89
|
+
}
|
|
61
90
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
62
91
|
// Type-Dispatched Augmentation
|
|
63
92
|
// ─────────────────────────────────────────────────────────────────────────────
|
package/dist/mcp/guidance.js
CHANGED
|
@@ -198,7 +198,7 @@ function formatToolSection(tg) {
|
|
|
198
198
|
}
|
|
199
199
|
if (tg.toolName === "feedback") {
|
|
200
200
|
extras.push("\nYour feedback is automatically collected and analyzed to improve the toolkit for all agents.");
|
|
201
|
-
extras.push("Include `knowledge_ref
|
|
201
|
+
extras.push("Include `knowledge_ref` to correlate feedback with specific knowledge documents — this drives both confidence scoring (MCP-side) and DE native ranking (UserEvents). All categories emit DE events: positive feedback sends conversion signals, negative feedback (correction, confusion, gap) sends zero-value conversions so DE learns what didn't work. Use `#fragment` for sub-document precision: `knowledge_ref=\"topic_auth#L51-L56\"` (lines), `knowledge_ref=\"topic_auth#authentication\"` (section).");
|
|
202
202
|
}
|
|
203
203
|
return `## ${title}
|
|
204
204
|
Use the \`${tg.toolName}\` tool${tg.quickTip ? `. ${tg.quickTip}` : ""}:
|
|
@@ -16,9 +16,8 @@ import { submitFeedback, listFeedback, listTelemetry, analyzeFeedback, rotateLog
|
|
|
16
16
|
import { markProbeResponded } from "./probes.js";
|
|
17
17
|
import { appendToOutbox, flushOutbox, getOutboxStats, readLocalMessages } from "./outbox.js";
|
|
18
18
|
import { isRemoteEnabled } from "./remote-store.js";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { getAttributionToken } from "../knowledge/session-state.js";
|
|
19
|
+
import { stripFragment } from "../../../knowledge/pipeline/confidence.js";
|
|
20
|
+
import { emitKnowledgeUserEvent, categoryToEvent } from "../knowledge/user-events.js";
|
|
22
21
|
import { analyzeGlobal } from "./global-analysis.js";
|
|
23
22
|
import { TOOLKIT_VERSION } from "../env/config.js";
|
|
24
23
|
const VALID_CATEGORIES = ALL_CATEGORIES;
|
|
@@ -134,43 +133,25 @@ async function handleSubmit(args) {
|
|
|
134
133
|
}
|
|
135
134
|
}
|
|
136
135
|
// Confidence loop: if feedback references a knowledge doc, update its confidence in DE
|
|
136
|
+
// Strip #fragment — confidence scoring operates at the document level
|
|
137
137
|
let confidenceUpdate;
|
|
138
|
-
|
|
138
|
+
const docId = knowledgeRef ? stripFragment(knowledgeRef) : undefined;
|
|
139
|
+
if (docId && CONFIDENCE_CATEGORIES.includes(category)) {
|
|
139
140
|
try {
|
|
140
141
|
const { processConfidenceFeedback } = await import("../knowledge/confidence-loop.js");
|
|
141
|
-
confidenceUpdate = await processConfidenceFeedback(category,
|
|
142
|
+
confidenceUpdate = await processConfidenceFeedback(category, docId, qualityData) ?? undefined;
|
|
142
143
|
}
|
|
143
144
|
catch {
|
|
144
145
|
// Best-effort — don't block feedback submission
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
|
-
// UserEvent emission: fire DE
|
|
148
|
+
// UserEvent emission: fire DE event for ALL feedback with knowledge_ref.
|
|
149
|
+
// Positive → conversion (value 1.0), negative → conversion (value 0.0), neutral → view-item.
|
|
148
150
|
// Independent of confidence loop — no guards, no cooldown. Fire-and-forget.
|
|
149
|
-
if (
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
&& (qualityData?.usefulness ?? 0) >= 4;
|
|
154
|
-
const isInteraction = category === "interaction";
|
|
155
|
-
if (isSuccess || isHighQuality || isInteraction) {
|
|
156
|
-
const conversionType = isSuccess ? "knowledge-success"
|
|
157
|
-
: isHighQuality ? "knowledge-quality-high"
|
|
158
|
-
: undefined; // interaction → view-item, no conversionType
|
|
159
|
-
getOrCreateClientId()
|
|
160
|
-
.then((clientId) => {
|
|
161
|
-
const token = getAttributionToken(knowledgeRef);
|
|
162
|
-
writeUserEvent({
|
|
163
|
-
eventType: conversionType ? "conversion" : "view-item",
|
|
164
|
-
userPseudoId: clientId,
|
|
165
|
-
...(token ? { attributionToken: token } : {}),
|
|
166
|
-
documents: [{
|
|
167
|
-
id: knowledgeRef,
|
|
168
|
-
...(conversionType ? { conversionValue: isSuccess ? 1.0 : 0.8 } : {}),
|
|
169
|
-
}],
|
|
170
|
-
...(conversionType ? { conversionType } : {}),
|
|
171
|
-
}).catch(() => { });
|
|
172
|
-
})
|
|
173
|
-
.catch(() => { });
|
|
151
|
+
if (docId) {
|
|
152
|
+
const eventOpts = categoryToEvent(category, qualityData);
|
|
153
|
+
if (eventOpts) {
|
|
154
|
+
emitKnowledgeUserEvent({ ...eventOpts, docIds: [docId] });
|
|
174
155
|
}
|
|
175
156
|
}
|
|
176
157
|
return {
|
|
@@ -22,7 +22,7 @@ import { SESSION_ID } from "./session.js";
|
|
|
22
22
|
// Category classification lives in confidence.ts (knowledge pipeline layer)
|
|
23
23
|
// Re-export here so feedback handlers can use it without reaching into pipeline internals
|
|
24
24
|
export { FeedbackSignal, CATEGORY_SIGNAL, } from "../../../knowledge/pipeline/confidence.js";
|
|
25
|
-
import { CATEGORY_SIGNAL, FeedbackSignal } from "../../../knowledge/pipeline/confidence.js";
|
|
25
|
+
import { CATEGORY_SIGNAL, FeedbackSignal, stripFragment, extractFragment } from "../../../knowledge/pipeline/confidence.js";
|
|
26
26
|
/** All valid category names — derived from the signal map */
|
|
27
27
|
export const ALL_CATEGORIES = Object.keys(CATEGORY_SIGNAL);
|
|
28
28
|
/** Categories that affect confidence scoring (negative, positive, or quality) */
|
|
@@ -359,28 +359,42 @@ export async function analyzeFeedback(rootOverride) {
|
|
|
359
359
|
};
|
|
360
360
|
deduplicateEntries(feedback.filter((e) => e.category === "gap"), "Documentation gap");
|
|
361
361
|
deduplicateEntries(feedback.filter((e) => e.category === "confusion"), "Unclear guidance");
|
|
362
|
-
// Graph-correlated insights: group feedback by knowledge_ref
|
|
362
|
+
// Graph-correlated insights: group feedback by knowledge_ref (doc-level)
|
|
363
|
+
// Fragment hotspots tracked per document (e.g., "L51-L56": 3)
|
|
363
364
|
const graphCorrelations = {};
|
|
364
365
|
for (const entry of feedback) {
|
|
365
366
|
const ref = entry.quality_data?.knowledge_ref;
|
|
366
367
|
if (!ref)
|
|
367
368
|
continue;
|
|
368
|
-
|
|
369
|
-
|
|
369
|
+
const docId = stripFragment(ref);
|
|
370
|
+
const fragment = extractFragment(ref);
|
|
371
|
+
if (!graphCorrelations[docId]) {
|
|
372
|
+
graphCorrelations[docId] = { count: 0, categories: {} };
|
|
373
|
+
}
|
|
374
|
+
graphCorrelations[docId].count++;
|
|
375
|
+
graphCorrelations[docId].categories[entry.category] =
|
|
376
|
+
(graphCorrelations[docId].categories[entry.category] ?? 0) + 1;
|
|
377
|
+
if (fragment) {
|
|
378
|
+
if (!graphCorrelations[docId].fragments) {
|
|
379
|
+
graphCorrelations[docId].fragments = {};
|
|
380
|
+
}
|
|
381
|
+
graphCorrelations[docId].fragments[fragment] =
|
|
382
|
+
(graphCorrelations[docId].fragments[fragment] ?? 0) + 1;
|
|
370
383
|
}
|
|
371
|
-
graphCorrelations[ref].count++;
|
|
372
|
-
graphCorrelations[ref].categories[entry.category] =
|
|
373
|
-
(graphCorrelations[ref].categories[entry.category] ?? 0) + 1;
|
|
374
384
|
}
|
|
375
385
|
// Compute average accuracy for quality-rated nodes
|
|
376
386
|
for (const entry of feedback) {
|
|
377
387
|
const ref = entry.quality_data?.knowledge_ref;
|
|
378
388
|
if (!ref || !entry.quality_data?.accuracy)
|
|
379
389
|
continue;
|
|
380
|
-
const
|
|
390
|
+
const docId = stripFragment(ref);
|
|
391
|
+
const corr = graphCorrelations[docId];
|
|
381
392
|
if (!corr)
|
|
382
393
|
continue;
|
|
383
|
-
const qualityEntries = feedback.filter((e) =>
|
|
394
|
+
const qualityEntries = feedback.filter((e) => {
|
|
395
|
+
const eRef = e.quality_data?.knowledge_ref;
|
|
396
|
+
return eRef && stripFragment(eRef) === docId && e.quality_data?.accuracy != null;
|
|
397
|
+
});
|
|
384
398
|
corr.avgAccuracy =
|
|
385
399
|
qualityEntries.reduce((sum, e) => sum + (e.quality_data.accuracy ?? 0), 0) /
|
|
386
400
|
qualityEntries.length;
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { getConsultedDocs } from "./session-state.js";
|
|
22
22
|
import { processConfidenceFeedback } from "./confidence-loop.js";
|
|
23
|
-
import {
|
|
24
|
-
import { getOrCreateClientId } from "../feedback/client-id.js";
|
|
23
|
+
import { emitKnowledgeUserEvent } from "./user-events.js";
|
|
25
24
|
/** Weight multiplier per signal layer */
|
|
26
25
|
const LAYER_WEIGHTS = {
|
|
27
26
|
system: 0.1, // API accepted it — weakest signal
|
|
@@ -178,27 +177,17 @@ export async function emitOutcomeFeedback(event) {
|
|
|
178
177
|
}
|
|
179
178
|
}
|
|
180
179
|
}
|
|
181
|
-
// Fire UserEvents (
|
|
182
|
-
|
|
183
|
-
.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
...(isConversion ? { conversionValue } : {}),
|
|
193
|
-
}));
|
|
194
|
-
writeUserEvent({
|
|
195
|
-
eventType: isConversion ? "conversion" : "view-item",
|
|
196
|
-
userPseudoId: clientId,
|
|
197
|
-
...(isConversion ? { conversionType: eventType } : {}),
|
|
198
|
-
documents,
|
|
199
|
-
}).catch(() => { });
|
|
200
|
-
})
|
|
201
|
-
.catch(() => { });
|
|
180
|
+
// Fire UserEvents via centralized emitter (handles attribution tokens + fragment stripping)
|
|
181
|
+
const baseValue = outcome.quality === "success" ? 1.0
|
|
182
|
+
: outcome.quality === "partial" ? 0.5
|
|
183
|
+
: 0;
|
|
184
|
+
const conversionValue = baseValue * weight;
|
|
185
|
+
emitKnowledgeUserEvent({
|
|
186
|
+
docIds: [...docs],
|
|
187
|
+
signal: outcome.isPositive ? "positive" : "negative",
|
|
188
|
+
conversionType: eventType,
|
|
189
|
+
conversionValue,
|
|
190
|
+
});
|
|
202
191
|
console.error(`[OUTCOME-FEEDBACK] ${eventType} (layer=${layer}, weight=${weight}): ` +
|
|
203
192
|
`${docs.size} consulted docs, ${updates} confidence updates`);
|
|
204
193
|
return { docs_processed: docs.size, confidence_updates: updates, event_type: eventType };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized DE UserEvent emission for knowledge feedback signals.
|
|
3
|
+
*
|
|
4
|
+
* Single entry point for all feedback → DE UserEvent emission.
|
|
5
|
+
* Handles: attribution token lookup, fragment stripping, signal→event mapping,
|
|
6
|
+
* and fire-and-forget delivery.
|
|
7
|
+
*
|
|
8
|
+
* Ownership boundary:
|
|
9
|
+
* - MCP OWNS: interaction/feedback collection, signal classification
|
|
10
|
+
* - knowledge-service WILL OWN: DE transport, attribution, event schema
|
|
11
|
+
*
|
|
12
|
+
* TODO(knowledge-service): migrate DE transport when knowledge-service owns DE integration.
|
|
13
|
+
* MCP will continue to own feedback collection and signal classification.
|
|
14
|
+
* knowledge-service will own: writeUserEvent, attribution cache, event→DE mapping.
|
|
15
|
+
*/
|
|
16
|
+
import { writeUserEvent } from "../../../knowledge/search-client.js";
|
|
17
|
+
import { getOrCreateClientId } from "../feedback/client-id.js";
|
|
18
|
+
import { getAttributionToken } from "./session-state.js";
|
|
19
|
+
import { stripFragment } from "../../../knowledge/pipeline/confidence.js";
|
|
20
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
// Feedback category → signal mapping
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
// TODO(knowledge-service): signal classification stays in MCP (owns feedback semantics)
|
|
24
|
+
const CATEGORY_EVENT_MAP = {
|
|
25
|
+
// Positive signals
|
|
26
|
+
success: { signal: "positive", conversionType: "knowledge-success", value: 1.0 },
|
|
27
|
+
interaction: { signal: "neutral", conversionType: "", value: 0 },
|
|
28
|
+
// Negative signals
|
|
29
|
+
correction: { signal: "negative", conversionType: "knowledge-correction", value: 0.0 },
|
|
30
|
+
confusion: { signal: "negative", conversionType: "knowledge-confusion", value: 0.0 },
|
|
31
|
+
gap: { signal: "negative", conversionType: "knowledge-gap", value: 0.0 },
|
|
32
|
+
error_unclear: { signal: "negative", conversionType: "knowledge-error", value: 0.0 },
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Map a feedback category + quality data to UserEvent parameters.
|
|
36
|
+
* Returns undefined for categories that shouldn't emit events (e.g., suggestion, probe_response).
|
|
37
|
+
*/
|
|
38
|
+
export function categoryToEvent(category, qualityData) {
|
|
39
|
+
// Quality category: only emit for high-quality ratings
|
|
40
|
+
if (category === "quality") {
|
|
41
|
+
const isHighQuality = (qualityData?.accuracy ?? 0) >= 4
|
|
42
|
+
&& (qualityData?.usefulness ?? 0) >= 4;
|
|
43
|
+
if (!isHighQuality)
|
|
44
|
+
return undefined;
|
|
45
|
+
return {
|
|
46
|
+
docIds: [], // caller fills in
|
|
47
|
+
signal: "positive",
|
|
48
|
+
conversionType: "knowledge-quality-high",
|
|
49
|
+
conversionValue: 0.8,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const mapped = CATEGORY_EVENT_MAP[category];
|
|
53
|
+
if (!mapped)
|
|
54
|
+
return undefined;
|
|
55
|
+
return {
|
|
56
|
+
docIds: [], // caller fills in
|
|
57
|
+
signal: mapped.signal,
|
|
58
|
+
conversionType: mapped.conversionType || undefined,
|
|
59
|
+
conversionValue: mapped.value,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
63
|
+
// Centralized emitter
|
|
64
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
65
|
+
/**
|
|
66
|
+
* Emit a DE UserEvent for knowledge feedback signals.
|
|
67
|
+
*
|
|
68
|
+
* Handles attribution token lookup, fragment stripping, and fire-and-forget delivery.
|
|
69
|
+
* All feedback paths (explicit feedback, outcome feedback) should use this function
|
|
70
|
+
* instead of calling writeUserEvent directly.
|
|
71
|
+
*
|
|
72
|
+
* TODO(knowledge-service): DE transport (writeUserEvent, attribution) moves to shared lib.
|
|
73
|
+
* This function becomes: knowledgeService.emitFeedbackEvent({ docIds, signal, ... })
|
|
74
|
+
*/
|
|
75
|
+
export function emitKnowledgeUserEvent(opts) {
|
|
76
|
+
if (opts.docIds.length === 0)
|
|
77
|
+
return;
|
|
78
|
+
const isConversion = opts.signal !== "neutral";
|
|
79
|
+
const conversionValue = opts.conversionValue
|
|
80
|
+
?? (opts.signal === "positive" ? 1.0 : opts.signal === "negative" ? 0.0 : undefined);
|
|
81
|
+
getOrCreateClientId()
|
|
82
|
+
.then((clientId) => {
|
|
83
|
+
// Strip fragments and dedupe doc IDs
|
|
84
|
+
const seen = new Set();
|
|
85
|
+
const documents = [];
|
|
86
|
+
let attributionToken;
|
|
87
|
+
for (const rawId of opts.docIds) {
|
|
88
|
+
const docId = stripFragment(rawId);
|
|
89
|
+
if (!docId || seen.has(docId))
|
|
90
|
+
continue;
|
|
91
|
+
seen.add(docId);
|
|
92
|
+
// Use first available attribution token (all docs from same search share one)
|
|
93
|
+
if (!attributionToken) {
|
|
94
|
+
attributionToken = getAttributionToken(docId);
|
|
95
|
+
}
|
|
96
|
+
documents.push({
|
|
97
|
+
id: docId,
|
|
98
|
+
...(isConversion && conversionValue !== undefined ? { conversionValue } : {}),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (documents.length === 0)
|
|
102
|
+
return;
|
|
103
|
+
writeUserEvent({
|
|
104
|
+
eventType: isConversion ? "conversion" : "view-item",
|
|
105
|
+
userPseudoId: clientId,
|
|
106
|
+
...(attributionToken ? { attributionToken } : {}),
|
|
107
|
+
...(isConversion && opts.conversionType ? { conversionType: opts.conversionType } : {}),
|
|
108
|
+
documents,
|
|
109
|
+
}).catch(() => { });
|
|
110
|
+
})
|
|
111
|
+
.catch(() => { });
|
|
112
|
+
}
|
|
@@ -692,13 +692,22 @@ The toolkit also collects anonymous telemetry (tool usage, error rates, latency)
|
|
|
692
692
|
|
|
693
693
|
## Knowledge Confidence Scoring
|
|
694
694
|
|
|
695
|
-
Feedback drives
|
|
696
|
-
- knowledge_ref="<document_id>" — top-level parameter, triggers real-time confidence update
|
|
697
|
-
- Negative categories (downgrade score): gap, confusion, correction, error_unclear
|
|
698
|
-
- Positive categories (upgrade score): success, interaction
|
|
699
|
-
- Nuanced: quality (depends on accuracy/usefulness scores — <3 negative, >=4 positive, no scores = neutral)
|
|
695
|
+
Feedback drives two parallel DE ranking signals when knowledge_ref is provided:
|
|
700
696
|
|
|
701
|
-
|
|
697
|
+
1. **Confidence Loop (MCP-side)** — immediate structData.confidence_score update via PATCH
|
|
698
|
+
2. **DE UserEvents (native ranking)** — conversion/view-item events for DE's ML-based ranking
|
|
699
|
+
|
|
700
|
+
All feedback categories emit DE UserEvents:
|
|
701
|
+
- Positive (conversion, value=1.0): success, interaction
|
|
702
|
+
- Negative (conversion, value=0.0): correction, confusion, gap, error_unclear
|
|
703
|
+
- Quality: depends on accuracy/usefulness scores (>=4 both → positive at 0.8, else skipped)
|
|
704
|
+
- Neutral (view-item): interaction
|
|
705
|
+
|
|
706
|
+
Include knowledge_ref to correlate feedback with specific documents:
|
|
707
|
+
- knowledge_ref="<document_id>" — triggers both confidence update and DE event
|
|
708
|
+
- Use #fragment for sub-document precision: knowledge_ref="topic_auth#L51-L56"
|
|
709
|
+
|
|
710
|
+
Labels derived from confidence score thresholds:
|
|
702
711
|
- verified (>= 0.80) — no significant negative feedback
|
|
703
712
|
- inferred (0.50–0.79) — some negative feedback received
|
|
704
713
|
- low-confidence (< 0.50) — multiple negative reports, auto-downgraded in search
|
package/dist/mcp/tools.js
CHANGED
|
@@ -786,10 +786,11 @@ A **profile** = tenant + environment + auth. Example: "acme-corp-prod" = Acme Co
|
|
|
786
786
|
- \`feedback(method="submit", category="suggestion", message="...")\` - suggest improvements
|
|
787
787
|
- \`feedback(method="submit", category="probe_response", message="<answer>", context="<probe.id>")\` - respond to a _probe question
|
|
788
788
|
- \`feedback(method="submit", category="quality", message="...", knowledge_ref="topic_authentication")\` - rate knowledge accuracy (drives confidence scoring)
|
|
789
|
-
- \`feedback(method="submit", category="correction", message="...", knowledge_ref="rule_get-before-modify")\` - report wrong
|
|
789
|
+
- \`feedback(method="submit", category="correction", message="...", knowledge_ref="rule_get-before-modify#L51-L56")\` - report wrong info at specific lines
|
|
790
|
+
- \`feedback(method="submit", category="correction", message="...", knowledge_ref="topic_auth#authentication")\` - report issue in a section
|
|
790
791
|
|
|
791
792
|
## Knowledge Confidence
|
|
792
|
-
Include \`knowledge_ref\` to correlate feedback with specific knowledge documents. Documents with high negative feedback are auto-downgraded in search results.
|
|
793
|
+
Include \`knowledge_ref\` to correlate feedback with specific knowledge documents. Use \`#fragment\` for sub-document precision (e.g., \`topic_auth#L51-L56\` for lines, \`topic_auth#authentication\` for sections). Documents with high negative feedback are auto-downgraded in search results.
|
|
793
794
|
|
|
794
795
|
## Review Feedback
|
|
795
796
|
- \`feedback(method="list")\` - view recent feedback
|
|
@@ -842,7 +843,7 @@ Messages are emitted by MCP response actions (search, publish, deploy) and stay
|
|
|
842
843
|
},
|
|
843
844
|
knowledge_ref: {
|
|
844
845
|
type: "string",
|
|
845
|
-
description: "Knowledge document ID
|
|
846
|
+
description: "Knowledge document ID, optionally with #fragment for sub-document precision. Examples: 'topic_auth', 'topic_auth#L51-L56' (lines), 'topic_auth#L51' (single line), 'topic_auth#authentication' (section). Same as GitHub URL fragments. Drives confidence scoring at the document level (fragment stripped for scoring, preserved for reporting).",
|
|
846
847
|
},
|
|
847
848
|
severity: {
|
|
848
849
|
type: "string",
|
package/package.json
CHANGED