aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/nodes/ChatBox/Agent.ts20
-rw-r--r--src/client/views/nodes/ChatBox/AnswerParser.ts84
-rw-r--r--src/client/views/nodes/ChatBox/ChatBox.tsx19
-rw-r--r--src/client/views/nodes/ChatBox/MessageComponent.tsx84
-rw-r--r--src/client/views/nodes/ChatBox/tools/RAGTool.ts106
-rw-r--r--src/client/views/nodes/ChatBox/types.ts15
6 files changed, 237 insertions, 91 deletions
diff --git a/src/client/views/nodes/ChatBox/Agent.ts b/src/client/views/nodes/ChatBox/Agent.ts
index d494928f9..ca1b5c60c 100644
--- a/src/client/views/nodes/ChatBox/Agent.ts
+++ b/src/client/views/nodes/ChatBox/Agent.ts
@@ -43,14 +43,14 @@ export class Agent {
console.log(`System prompt: ${systemPrompt}`);
this.interMessages = [{ role: 'system', content: systemPrompt }];
- this.interMessages.push({ role: 'user', content: `<query>${question}</query>` });
+ this.interMessages.push({ role: 'user', content: `<step0 role="user"><query>${question}</query></step>` });
const parser = new XMLParser();
const builder = new XMLBuilder();
let currentAction: string | undefined;
- for (let i = 0; i < maxTurns; i++) {
- console.log(`Turn ${i + 1}/${maxTurns}`);
+ for (let i = 1; i < maxTurns; i++) {
+ console.log(`Turn ${i}/${maxTurns}`);
const result = await this.execute();
console.log(`Bot response: ${result}`);
@@ -73,17 +73,20 @@ export class Agent {
currentAction = step[key] as string;
console.log(`Action: ${currentAction}`);
if (this.tools[currentAction]) {
+ i++;
const nextPrompt = [
{
type: 'text',
- text: builder.build({ action_rules: this.tools[currentAction].getActionRule() }),
+ text: `<step${i} role="user">` + builder.build({ action_rules: this.tools[currentAction].getActionRule() }) + `<\step>`,
},
];
this.interMessages.push({ role: 'user', content: nextPrompt });
+
break;
} else {
console.log('Error: No valid action');
- this.interMessages.push({ role: 'user', content: 'No valid action, try again.' });
+ i++;
+ this.interMessages.push({ role: 'user', content: `<step${i}>No valid action, try again.</step>` });
break;
}
} else if (key === 'action_input') {
@@ -92,7 +95,12 @@ export class Agent {
if (currentAction) {
try {
const observation = await this.processAction(currentAction, step[key]);
- const nextPrompt = [{ type: 'text', text: '<observation>' }, ...observation, { type: 'text', text: '</observation>' }];
+ // const stepElement = parsedResult.documentElement;
+ // const rootTagName = stepElement.tagName;
+ // const match = rootTagName.match(/step(\d+)/);
+ // const currentStep = match ? parseInt(match[1]) + 1 : 1;
+ i++;
+ const nextPrompt = [{ type: 'text', text: `<step${i}<observation>` }, ...observation, { type: 'text', text: '</observation></step>' }];
console.log(observation);
this.interMessages.push({ role: 'user', content: nextPrompt });
break;
diff --git a/src/client/views/nodes/ChatBox/AnswerParser.ts b/src/client/views/nodes/ChatBox/AnswerParser.ts
index 1162d46b0..4b6c817fd 100644
--- a/src/client/views/nodes/ChatBox/AnswerParser.ts
+++ b/src/client/views/nodes/ChatBox/AnswerParser.ts
@@ -1,12 +1,13 @@
-import { ASSISTANT_ROLE, AssistantMessage, Citation, getChunkType } from './types';
+import { ASSISTANT_ROLE, AssistantMessage, Citation, CHUNK_TYPE, TEXT_TYPE, getChunkType } from './types';
import { v4 as uuid } from 'uuid';
export class AnswerParser {
static parse(xml: string): AssistantMessage {
const answerRegex = /<answer>([\s\S]*?)<\/answer>/;
- const citationRegex = /<citation chunk_id="([^"]+)" type="([^"]+)">(.*?)<\/citation>/g;
+ const citationRegex = /<citation index="([^"]+)" chunk_id="([^"]+)" type="([^"]+)">([\s\S]*?)<\/citation>/g;
const followUpQuestionsRegex = /<follow_up_questions>([\s\S]*?)<\/follow_up_questions>/;
const questionRegex = /<question>(.*?)<\/question>/g;
+ const groundedTextRegex = /<grounded_text citation_index="([^"]+)">([\s\S]*?)<\/grounded_text>/g;
const answerMatch = answerRegex.exec(xml);
const followUpQuestionsMatch = followUpQuestionsRegex.exec(xml);
@@ -16,45 +17,86 @@ export class AnswerParser {
}
const rawTextContent = answerMatch[1].trim();
- const textContentWithCitations = rawTextContent.replace(citationRegex, '');
- const textContent = textContentWithCitations.replace(followUpQuestionsRegex, '').trim();
-
+ let textContent: AssistantMessage['content'] = [];
let citations: Citation[] = [];
- let match: RegExpExecArray | null;
-
- let plainTextOffset = 0;
- let citationOffset = 0;
-
- while ((match = citationRegex.exec(rawTextContent)) !== null) {
- const [fullMatch, chunk_id, type, direct_text] = match;
- const citationStartIndex = match.index;
- const citationPlainStart = citationStartIndex - citationOffset;
+ let contentIndex = 0;
+ // Parse citations
+ let citationMatch;
+ while ((citationMatch = citationRegex.exec(rawTextContent)) !== null) {
+ const [_, index, chunk_id, type, direct_text] = citationMatch;
citations.push({
direct_text: direct_text.trim(),
type: getChunkType(type),
- chunk_id: chunk_id,
- text_location: citationPlainStart,
+ chunk_id,
citation_id: uuid(),
});
+ }
- citationOffset += fullMatch.length;
+ // Parse text content (normal and grounded)
+ let lastIndex = 0;
+ let matches = [];
+
+ // Find all grounded text matches
+ let groundedTextMatch;
+ while ((groundedTextMatch = groundedTextRegex.exec(rawTextContent)) !== null) {
+ matches.push({
+ type: 'grounded',
+ index: groundedTextMatch.index,
+ length: groundedTextMatch[0].length,
+ citationIndexes: groundedTextMatch[1],
+ text: groundedTextMatch[2],
+ });
+ }
+
+ // Sort matches by their index in the original text
+ matches.sort((a, b) => a.index - b.index);
+
+ // Process normal and grounded text in order
+ for (let i = 0; i <= matches.length; i++) {
+ const currentMatch = matches[i];
+ const nextMatchIndex = currentMatch ? currentMatch.index : rawTextContent.length;
+
+ // Add normal text before the current grounded text (or end of content)
+ if (nextMatchIndex > lastIndex) {
+ const normalText = rawTextContent.slice(lastIndex, nextMatchIndex).trim();
+ if (normalText) {
+ textContent.push({
+ index: contentIndex++,
+ type: TEXT_TYPE.NORMAL,
+ text: normalText,
+ citation_ids: null,
+ });
+ }
+ }
+
+ // Add grounded text if there's a match
+ if (currentMatch) {
+ const citationIds = currentMatch.citationIndexes.split(',').map(index => citations[parseInt(index) - 1].citation_id);
+ textContent.push({
+ index: contentIndex++,
+ type: TEXT_TYPE.GROUNDED,
+ text: currentMatch.text.trim(),
+ citation_ids: citationIds,
+ });
+ lastIndex = currentMatch.index + currentMatch.length;
+ }
}
let followUpQuestions: string[] = [];
if (followUpQuestionsMatch) {
const questionsText = followUpQuestionsMatch[1];
- let questionMatch: RegExpExecArray | null;
-
+ let questionMatch;
while ((questionMatch = questionRegex.exec(questionsText)) !== null) {
followUpQuestions.push(questionMatch[1].trim());
}
}
+
const assistantResponse: AssistantMessage = {
role: ASSISTANT_ROLE.ASSISTANT,
- text_content: textContent,
+ content: textContent,
follow_up_questions: followUpQuestions,
- citations: citations,
+ citations,
};
return assistantResponse;
diff --git a/src/client/views/nodes/ChatBox/ChatBox.tsx b/src/client/views/nodes/ChatBox/ChatBox.tsx
index 49c9b3292..9e604073d 100644
--- a/src/client/views/nodes/ChatBox/ChatBox.tsx
+++ b/src/client/views/nodes/ChatBox/ChatBox.tsx
@@ -11,7 +11,7 @@ import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { FieldView, FieldViewProps } from '../FieldView';
import './ChatBox.scss';
import MessageComponentBox from './MessageComponent';
-import { ASSISTANT_ROLE, AssistantMessage, AI_Document, Citation, CHUNK_TYPE, Chunk, getChunkType } from './types';
+import { ASSISTANT_ROLE, AssistantMessage, AI_Document, Citation, CHUNK_TYPE, Chunk, getChunkType, TEXT_TYPE } from './types';
import { Vectorstore } from './vectorstore/VectorstoreUpload';
import { Agent } from './Agent';
import dotenv from 'dotenv';
@@ -58,7 +58,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.agent = new Agent(this.vectorstore, this.retrieveSummaries, this.retrieveFormattedHistory);
reaction(
- () => this.history.map((msg: AssistantMessage) => ({ role: msg.role, text_content: msg.text_content, follow_up_questions: msg.follow_up_questions, citations: msg.citations })),
+ () => this.history.map((msg: AssistantMessage) => ({ role: msg.role, content: msg.content, follow_up_questions: msg.follow_up_questions, citations: msg.citations })),
serializableHistory => {
this.dataDoc.data = JSON.stringify(serializableHistory);
}
@@ -101,7 +101,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
try {
textInput.value = '';
runInAction(() => {
- this.history.push({ role: ASSISTANT_ROLE.USER, text_content: trimmedText });
+ this.history.push({ role: ASSISTANT_ROLE.USER, content: [{ index: 0, type: TEXT_TYPE.NORMAL, text: trimmedText, citation_ids: null }] });
this.isLoading = true;
});
@@ -113,7 +113,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
} catch (err) {
console.error('Error:', err);
runInAction(() => {
- this.history.push({ role: ASSISTANT_ROLE.ASSISTANT, text_content: 'Sorry, I encountered an error while processing your request.' });
+ this.history.push({ role: ASSISTANT_ROLE.USER, content: [{ index: 0, type: TEXT_TYPE.NORMAL, text: 'Sorry, I encountered an error while processing your request.', citation_ids: null }] });
});
} finally {
runInAction(() => {
@@ -212,7 +212,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
this.history.push(
...storedHistory.map((msg: AssistantMessage) => ({
role: msg.role,
- text_content: msg.text_content,
+ content: msg.content,
follow_up_questions: msg.follow_up_questions,
citations: msg.citations,
}))
@@ -222,7 +222,12 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
console.error('Failed to parse history from dataDoc:', e);
}
} else {
- this.history = [{ role: ASSISTANT_ROLE.ASSISTANT, text_content: 'Welcome to the Document Analyser Assistant! Link a document or ask questions to get started.' }];
+ runInAction(() => {
+ this.history.push({
+ role: ASSISTANT_ROLE.USER,
+ content: [{ index: 0, type: TEXT_TYPE.NORMAL, text: 'Welcome to the Document Analyser Assistant! Link a document or ask questions to get started.', citation_ids: null }],
+ });
+ });
}
reaction(
() => {
@@ -276,7 +281,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
get formattedHistory(): string {
let history = '<chat_history>\n';
for (const message of this.history) {
- history += `<${message.role}>${message.text_content}</${message.role}>\n`;
+ history += `<${message.role}>${message.content.map(content => content.text).join(' ')}</${message.role}>\n`;
}
history += '</chat_history>';
return history;
diff --git a/src/client/views/nodes/ChatBox/MessageComponent.tsx b/src/client/views/nodes/ChatBox/MessageComponent.tsx
index 56fde8bb2..fd7c445c5 100644
--- a/src/client/views/nodes/ChatBox/MessageComponent.tsx
+++ b/src/client/views/nodes/ChatBox/MessageComponent.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { observer } from 'mobx-react';
-import { AssistantMessage, Citation } from './types';
+import { AssistantMessage, Citation, MessageContent, TEXT_TYPE } from './types';
import Markdown from 'react-markdown';
interface MessageComponentProps {
@@ -12,53 +12,51 @@ interface MessageComponentProps {
}
const MessageComponentBox: React.FC<MessageComponentProps> = function ({ message, index, onFollowUpClick, onCitationClick, updateMessageCitations }) {
- const renderContent = (content: string) => {
- if (!message.citations || message.citations.length === 0) {
- return content;
- }
-
- const parts = [];
- let lastIndex = 0;
-
- message.citations.forEach((citation, idx) => {
- const location = citation.text_location;
- const textBefore = content.slice(lastIndex, location);
- const citationButton = (
- <button
- key={idx}
- className="citation-button"
- onClick={() => onCitationClick(citation)}
- style={{
- display: 'inline-flex',
- alignItems: 'center',
- justifyContent: 'center',
- width: '20px',
- height: '20px',
- borderRadius: '50%',
- border: 'none',
- background: '#ff6347',
- color: 'white',
- fontSize: '12px',
- fontWeight: 'bold',
- cursor: 'pointer',
- margin: '0 2px',
- padding: 0,
- }}>
- {idx + 1}
- </button>
+ const renderContent = (item: MessageContent) => {
+ const i = item.index;
+ if (item.type === TEXT_TYPE.GROUNDED) {
+ const citation_ids = item.citation_ids || [];
+ return (
+ <span key={i} className="grounded-text">
+ {item.text}
+ {citation_ids.map((id, idx) => {
+ const citation = message.citations?.find(c => c.citation_id === id);
+ if (!citation) return null;
+ return (
+ <button
+ key={idx}
+ className="citation-button"
+ onClick={() => onCitationClick(citation)}
+ style={{
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: '20px',
+ height: '20px',
+ borderRadius: '50%',
+ border: 'none',
+ background: '#ff6347',
+ color: 'white',
+ fontSize: '12px',
+ fontWeight: 'bold',
+ cursor: 'pointer',
+ margin: '0 2px',
+ padding: 0,
+ }}>
+ {idx + 1}
+ </button>
+ );
+ })}
+ </span>
);
- parts.push(textBefore, citationButton);
- lastIndex = location;
- });
-
- parts.push(content.slice(lastIndex));
-
- return parts;
+ } else {
+ return <span key={i}>{item.text}</span>;
+ }
};
return (
<div className={`message ${message.role}`}>
- <div>{renderContent(message.text_content)}</div>
+ <div>{message.content && message.content.map(messageFragment => <React.Fragment key={messageFragment.index}>{renderContent(messageFragment)}</React.Fragment>)}</div>
{message.follow_up_questions && message.follow_up_questions.length > 0 && (
<div className="follow-up-questions">
<h4>Follow-up Questions:</h4>
diff --git a/src/client/views/nodes/ChatBox/tools/RAGTool.ts b/src/client/views/nodes/ChatBox/tools/RAGTool.ts
index 5bc31dbab..4b29d6bce 100644
--- a/src/client/views/nodes/ChatBox/tools/RAGTool.ts
+++ b/src/client/views/nodes/ChatBox/tools/RAGTool.ts
@@ -17,26 +17,108 @@ export class RAGTool extends BaseTool<{ hypothetical_document_chunk: string }> {
required: 'true',
},
},
- `Your task is to first provide a response to the user's prompt based on the information given in the chunks and considering the chat history. Follow these steps:
+ `
+ Your task is to provide a comprehensive response to the user's prompt based on the given chunks and chat history. Follow these structural guidelines meticulously:
- 1. Carefully read and analyze the provided chunks, which may include text, images, or tables. Each chunk has an associated chunk_id.
+ 1. Overall Structure:
+ <answer>
+ [Main content with nested grounded_text tags]
+ <citations>
+ [Individual citation tags]
+ </citations>
+ <follow_up_questions>
+ [Three question tags]
+ </follow_up_questions>
+ </answer>
- 2. Review the prompt and chat history to understand the context of the user's question or request.
+ 2. Grounded Text Tag Structure:
+ - Basic format:
+ <grounded_text citation_index="[index number(s)]">
+ [Your generated text based on chunk information]
+ </grounded_text>
- 3. Formulate a response that addresses the prompt using information from the relevant chunks. Your response should be informative and directly answer the user's question or request.
+ - Nested format:
+ <grounded_text citation_index="[index number(s)]">
+ [General information]
+ <grounded_text citation_index="[index number(s)]">
+ [More specific information]
+ </grounded_text>
+ </grounded_text>
- 4. Use citations to support your response. Citations should contain direct textual references to the granular, specific part of the original chunk that applies to the situation—with no text ommitted. Citations should be in the following format:
- - For text: <citation chunk_id="d980c2a7-cad3-4d7e-9eae-19bd2380bd02" type="text">relevant direct text from the chunk that the citation in referencing specifically</citation>
- - For images or tables: <citation chunk_id="9ef37681-b57e-4424-b877-e1ebc326ff11" type="image"></citation>
+ - Multiple citation indices:
+ <grounded_text citation_index="1,2,3">
+ [Information synthesized from multiple chunks]
+ </grounded_text>
- Place citations after the sentences they apply to. You can use multiple citations in a row.
+ 3. Citation Tag Structure:
+ <citation index="[unique number]" chunk_id="[UUID v4]" type="[text/image/table]">
+ [For text: relevant subset of original chunk]
+ [For image/table: leave empty]
+ </citation>
- 5. If there's insufficient information in the provided chunks to answer the prompt sufficiently, ALWAYS respond with <answer>RAG not applicable</answer>
+ 4. Detailed Grounded Text Guidelines:
+ a. Wrap all information derived from chunks in grounded_text tags.
+ b. Nest grounded_text tags when presenting hierarchical or increasingly specific information or when a larger section of generated text is best grounded by one subset of a chunk and smaller sections of that generated text are best grounded by other subsets of either the same or different chunk(s).
+ c. Use a single grounded_text tag for closely related information that references the same citation (subset of text from a chunk).
+ d. Combine multiple citation indices for synthesized information from multiple citations.
+ e. Ensure every grounded_text tag has at least one corresponding citation.
+ f. Grounded text can be as short as a few words or as long as several sentences.
+ d. Avoid overlapping grounded_text tags; instead, use nesting or sequential tags.
- Write your entire response, including follow-up questions, inside <answer> tags. Remember to use the citation format for both text and image references, and maintain a conversational tone throughout your response.
+ 5. Detailed Citation Guidelines:
+ a. Create a unique citation for each distinct piece of information from the chunks that is used to support grounded_text.
+ b. Ensure each citation has a unique index number.
+ c. Specify the correct type: "text", "image", or "table".
+ d. For text chunks, include only the relevant subset of the original text that the grounded_text is based on.
+ e. For image/table chunks, leave the citation content empty.
+ f. One citation can be used for multiple grounded_text tags if they are based on the same information.
+ g. One text chunk can have multiple citations if different parts of the text have different important information.
+ h. !!!DO NOT OVERCITE - only include citations for information that is directly relevant to the grounded_text.
- !!!IMPORTANT Before you close the tag with </answer>, within the answer tags provide a set of 3 follow-up questions inside a <follow_up_questions> tag and individually within <question> tags. These should relate to the document, the current query, and the chat_history and should aim to help the user better understand whatever they are looking for.
- Also, ensure that the answer tags are wrapped with the correct step tags as well.`,
+ 6. Structural Integrity Checks:
+ a. Ensure all opening tags have corresponding closing tags.
+ b. Verify that all grounded_text tags have valid citation_index attributes.
+ c. Check that all cited indices in grounded_text tags have corresponding citations.
+ d. Confirm proper nesting - tags opened last should be closed first.
+
+ Example of grounded_text usage:
+
+ <answer>
+ <grounded_text citation_index="1,2">
+ Artificial Intelligence (AI) is revolutionizing various sectors, with healthcare experiencing significant transformations in areas such as diagnosis and treatment planning.
+ <grounded_text citation_index="2,3,4">
+ In the field of medical diagnosis, AI has shown remarkable capabilities, particularly in radiology. For instance, AI systems have drastically improved mammogram analysis, achieving 99% accuracy at a rate 30 times faster than human radiologists.
+ <grounded_text citation_index="4">
+ This advancement not only enhances the efficiency of healthcare systems but also significantly reduces the occurrence of false positives, leading to fewer unnecessary biopsies and reduced patient stress.
+ </grounded_text>
+ </grounded_text>
+ </grounded_text>
+
+ <grounded_text citation_index="5,6">
+ Beyond diagnosis, AI is playing a crucial role in drug discovery and development. By analyzing vast amounts of genetic and molecular data, AI algorithms can identify potential drug candidates much faster than traditional methods.
+ <grounded_text citation_index="6">
+ This could potentially reduce the time and cost of bringing new medications to market, especially for rare diseases that have historically received less attention due to limited market potential.
+ </grounded_text>
+ </grounded_text>
+
+ [... rest of the content ...]
+
+ <citations>
+ <citation index="1" chunk_id="123e4567-e89b-12d3-a456-426614174000" type="text">Artificial Intelligence is revolutionizing various industries, with healthcare being one of the most profoundly affected sectors.</citation>
+ <citation index="2" chunk_id="123e4567-e89b-12d3-a456-426614174001" type="text">AI has shown particular promise in the field of radiology, enhancing the accuracy and speed of image analysis.</citation>
+ <citation index="3" chunk_id="123e4567-e89b-12d3-a456-426614174002" type="text">According to recent studies, AI systems have achieved 99% accuracy in mammogram analysis, performing the task 30 times faster than human radiologists.</citation>
+ <citation index="4" chunk_id="123e4567-e89b-12d3-a456-426614174003" type="text">The improvement in mammogram accuracy has led to a significant reduction in false positives, decreasing the need for unnecessary biopsies and reducing patient anxiety.</citation>
+ <citation index="5" chunk_id="123e4567-e89b-12d3-a456-426614174004" type="text">AI is accelerating the drug discovery process by analyzing complex molecular and genetic data to identify potential drug candidates.</citation>
+ <citation index="6" chunk_id="123e4567-e89b-12d3-a456-426614174005" type="text">The use of AI in drug discovery could significantly reduce the time and cost associated with bringing new medications to market, particularly for rare diseases.</citation>
+ </citations>
+
+ <follow_up_questions>
+ <question>How might AI-driven personalized medicine impact the cost and accessibility of healthcare in the future?</question>
+ <question>What measures can be taken to ensure that AI systems in healthcare are free from biases and equally effective for diverse populations?</question>
+ <question>How could the role of healthcare professionals evolve as AI becomes more integrated into medical practices?</question>
+ </follow_up_questions>
+ </answer>
+ `,
`Performs a RAG (Retrieval-Augmented Generation) search on user documents and returns a
set of document chunks (either images or text) that can be used to provide a grounded response based on
diff --git a/src/client/views/nodes/ChatBox/types.ts b/src/client/views/nodes/ChatBox/types.ts
index d702d5c41..10c80c05a 100644
--- a/src/client/views/nodes/ChatBox/types.ts
+++ b/src/client/views/nodes/ChatBox/types.ts
@@ -6,6 +6,11 @@ export enum ASSISTANT_ROLE {
ASSISTANT = 'assistant',
}
+export enum TEXT_TYPE {
+ NORMAL = 'normal',
+ GROUNDED = 'grounded',
+}
+
export enum CHUNK_TYPE {
TEXT = 'text',
IMAGE = 'image',
@@ -27,16 +32,22 @@ export function getChunkType(type: string): CHUNK_TYPE {
export interface AssistantMessage {
role: ASSISTANT_ROLE;
- text_content: string;
+ content: MessageContent[];
follow_up_questions?: string[];
citations?: Citation[];
}
+export interface MessageContent {
+ index: number;
+ type: TEXT_TYPE;
+ text: string;
+ citation_ids: string[] | null;
+}
+
export interface Citation {
direct_text?: string;
type: CHUNK_TYPE;
chunk_id: string;
- text_location: number;
citation_id: string;
}