@deepcitation/deepcitation-js 1.1.1 → 1.1.2
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/README.md +40 -17
- package/lib/parsing/parseCitation.js +13 -4
- package/lib/react/utils.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
|
|
19
19
|
LLMs hallucinate. Even when given source documents, they make up quotes, invent statistics, and cite pages that don't exist. DeepCitation solves this by **deterministically verifying every citation** against your source documents—and generating visual proof.
|
|
20
20
|
|
|
21
|
+
<div align="center">
|
|
22
|
+
<img src="./examples/assets/deepcitation-medical-demo.gif" alt="DeepCitation medical documentation demo showing verified inline citations" width="700" />
|
|
23
|
+
<br />
|
|
24
|
+
<em>Medical documentation with verified inline citations — certainty at a glance</em>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
21
27
|
```
|
|
22
28
|
Before: "Revenue grew 45% [1]" → ❓ Did the LLM make this up?
|
|
23
29
|
After: "Revenue grew 45% [1]" → ✅ Verified on page 3, line 12 (with screenshot)
|
|
@@ -59,7 +65,7 @@ import { DeepCitation, wrapCitationPrompt } from "@deepcitation/deepcitation-js"
|
|
|
59
65
|
|
|
60
66
|
const dc = new DeepCitation({ apiKey: process.env.DEEPCITATION_API_KEY });
|
|
61
67
|
|
|
62
|
-
// Upload source files
|
|
68
|
+
// Upload source files, this can be done before the user types their prompt
|
|
63
69
|
const { fileDataParts, deepTextPromptPortion } = await dc.prepareFiles([
|
|
64
70
|
{ file: pdfBuffer, filename: "report.pdf" },
|
|
65
71
|
]);
|
|
@@ -87,31 +93,49 @@ Verify citations against the source documents.
|
|
|
87
93
|
```typescript
|
|
88
94
|
const result = await dc.verifyCitations({
|
|
89
95
|
llmOutput: response.content,
|
|
90
|
-
fileDataParts,
|
|
96
|
+
fileDataParts, //optional
|
|
91
97
|
});
|
|
92
98
|
|
|
93
|
-
// result.
|
|
99
|
+
// result.verifications contains verification status + visual proof
|
|
100
|
+
const { citations, verifications } = result;
|
|
101
|
+
|
|
94
102
|
```
|
|
95
103
|
|
|
96
104
|
### Step 3: Display
|
|
97
105
|
|
|
98
|
-
|
|
106
|
+
Parse the LLM output and render verified citations inline with React components.
|
|
99
107
|
|
|
100
108
|
```tsx
|
|
101
109
|
import { CitationComponent } from "@deepcitation/deepcitation-js/react";
|
|
110
|
+
import {
|
|
111
|
+
parseCitation,
|
|
112
|
+
generateCitationKey,
|
|
113
|
+
} from "@deepcitation/deepcitation-js";
|
|
102
114
|
import "@deepcitation/deepcitation-js/react/styles.css";
|
|
103
115
|
|
|
104
|
-
function Response({
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
function Response({ llmOutput, verifications }) {
|
|
117
|
+
// Split LLM output by citation tags and render inline
|
|
118
|
+
const renderWithCitations = (text: string) => {
|
|
119
|
+
const parts = text.split(/(<cite\s+[^>]*\/>)/g);
|
|
120
|
+
|
|
121
|
+
return parts.map((part, index) => {
|
|
122
|
+
if (part.startsWith("<cite")) {
|
|
123
|
+
const { citation } = parseCitation(part);
|
|
124
|
+
const citationKey = generateCitationKey(citation);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<CitationComponent
|
|
128
|
+
key={index}
|
|
129
|
+
citation={citation}
|
|
130
|
+
verification={verifications[citationKey]}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return <span key={index}>{part}</span>;
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return <div>{renderWithCitations(llmOutput)}</div>;
|
|
115
139
|
}
|
|
116
140
|
```
|
|
117
141
|
|
|
@@ -123,8 +147,7 @@ function Response({ citations, verifications }) {
|
|
|
123
147
|
|
|
124
148
|
```typescript
|
|
125
149
|
const dc = new DeepCitation({
|
|
126
|
-
apiKey: string
|
|
127
|
-
apiUrl?: string, // Optional: Custom API URL
|
|
150
|
+
apiKey: string // Your API key (sk-dc-*)
|
|
128
151
|
});
|
|
129
152
|
|
|
130
153
|
// Upload and prepare source files
|
|
@@ -100,8 +100,9 @@ export const parseCitation = (fragment, mdAttachmentId, citationCounterRef, isVe
|
|
|
100
100
|
const citationMatches = [...middleCite.matchAll(citationRegex)];
|
|
101
101
|
const match = citationMatches?.[0];
|
|
102
102
|
const pageNumber = match?.[2] ? parseInt(match?.[2]) : undefined;
|
|
103
|
+
const pageIndex = match?.[3] ? parseInt(match?.[3]) : undefined;
|
|
103
104
|
let fileId = match?.[1];
|
|
104
|
-
let attachmentId = fileId?.length === 20 ? fileId : mdAttachmentId ||
|
|
105
|
+
let attachmentId = fileId?.length === 20 ? fileId : mdAttachmentId || fileId;
|
|
105
106
|
// Use helper to handle escaped quotes inside the phrase
|
|
106
107
|
let fullPhrase = cleanAndUnescape(match?.[4]);
|
|
107
108
|
let keySpan = cleanAndUnescape(match?.[5]);
|
|
@@ -138,8 +139,7 @@ export const parseCitation = (fragment, mdAttachmentId, citationCounterRef, isVe
|
|
|
138
139
|
let timestamps;
|
|
139
140
|
if (avMatch) {
|
|
140
141
|
fileId = avMatch?.[1];
|
|
141
|
-
attachmentId =
|
|
142
|
-
fileId?.length === 20 ? fileId : mdAttachmentId || avMatch?.[1];
|
|
142
|
+
attachmentId = fileId?.length === 20 ? fileId : mdAttachmentId || fileId;
|
|
143
143
|
fullPhrase = cleanAndUnescape(avMatch?.[2]);
|
|
144
144
|
const timestampsString = avMatch?.[3]?.replace(/timestamps=['"]|['"]/g, "");
|
|
145
145
|
const [startTime, endTime] = timestampsString?.split("-") || [];
|
|
@@ -156,6 +156,7 @@ export const parseCitation = (fragment, mdAttachmentId, citationCounterRef, isVe
|
|
|
156
156
|
const citation = {
|
|
157
157
|
fileId: attachmentId,
|
|
158
158
|
pageNumber,
|
|
159
|
+
startPageKey: `page_number_${pageNumber || 1}_index_${pageIndex || 0}`,
|
|
159
160
|
fullPhrase,
|
|
160
161
|
keySpan,
|
|
161
162
|
citationNumber,
|
|
@@ -194,13 +195,21 @@ const parseJsonCitation = (jsonCitation, citationNumber) => {
|
|
|
194
195
|
if (!fullPhrase) {
|
|
195
196
|
return null;
|
|
196
197
|
}
|
|
197
|
-
// Parse startPageKey format: "page_number_PAGE_index_INDEX"
|
|
198
|
+
// Parse startPageKey format: "page_number_PAGE_index_INDEX" or simple "PAGE_INDEX"
|
|
198
199
|
let pageNumber;
|
|
199
200
|
if (startPageKey) {
|
|
201
|
+
// Try full format first: page_number_5_index_2 or pageKey_5_index_2
|
|
200
202
|
const pageMatch = startPageKey.match(/page[_a-zA-Z]*(\d+)_index_(\d+)/i);
|
|
201
203
|
if (pageMatch) {
|
|
202
204
|
pageNumber = parseInt(pageMatch[1], 10);
|
|
203
205
|
}
|
|
206
|
+
else {
|
|
207
|
+
// Try simple n_m format: 5_4 (page 5, index 4)
|
|
208
|
+
const simpleMatch = startPageKey.match(/^(\d+)_(\d+)$/);
|
|
209
|
+
if (simpleMatch) {
|
|
210
|
+
pageNumber = parseInt(simpleMatch[1], 10);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
204
213
|
}
|
|
205
214
|
// Sort lineIds if present
|
|
206
215
|
const lineIds = rawLineIds?.length
|
package/lib/react/utils.js
CHANGED
|
@@ -8,8 +8,8 @@ export function generateCitationKey(citation) {
|
|
|
8
8
|
citation.fileId || "",
|
|
9
9
|
citation.pageNumber?.toString() || "",
|
|
10
10
|
citation.fullPhrase || "",
|
|
11
|
+
citation.keySpan?.toString() || "",
|
|
11
12
|
citation.value || "",
|
|
12
|
-
citation.citationNumber?.toString() || "",
|
|
13
13
|
citation.lineIds?.join(",") || "",
|
|
14
14
|
citation.timestamps?.startTime || "",
|
|
15
15
|
citation.timestamps?.endTime || "",
|