├── README.md ├── backend ├── .gitignore ├── README.md ├── app │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ └── routers │ │ │ ├── __init__.py │ │ │ └── chat.py │ └── utils │ │ ├── Test.ipynb │ │ ├── __init__.py │ │ ├── index.py │ │ ├── json.py │ │ ├── misc.py │ │ ├── node_parsers │ │ └── markdown.py │ │ ├── test.py │ │ ├── transformations │ │ ├── __init__.py │ │ ├── deduplicator.py │ │ ├── hyperlinks_remover.py │ │ ├── summarizer.py │ │ ├── upserter.py │ │ └── url_extractor.py │ │ └── vectorstores │ │ └── dummy.py ├── data │ └── docs │ │ ├── .gitignore │ │ ├── _static │ │ └── concepts │ │ │ ├── indexing.jpg │ │ │ ├── querying.jpg │ │ │ └── rag.jpg │ │ ├── getting_started │ │ ├── _category_.yml │ │ ├── concepts.md │ │ ├── environments.md │ │ ├── installation.mdx │ │ └── starter.mdx │ │ ├── introduction.md │ │ ├── modules │ │ ├── _category_.yml │ │ ├── agent │ │ │ ├── _category_.yml │ │ │ ├── index.md │ │ │ ├── multi_document_agent.mdx │ │ │ ├── openai.mdx │ │ │ ├── query_engine_tool.mdx │ │ │ └── react_agent.mdx │ │ ├── chat_engine.md │ │ ├── data_index.md │ │ ├── data_loader.mdx │ │ ├── documents_and_nodes │ │ │ ├── _category_.yml │ │ │ ├── index.md │ │ │ └── metadata_extraction.md │ │ ├── embeddings │ │ │ ├── _category_.yml │ │ │ ├── available_embeddings │ │ │ │ ├── _category_.yml │ │ │ │ ├── huggingface.md │ │ │ │ ├── mistral.md │ │ │ │ ├── ollama.md │ │ │ │ ├── openai.md │ │ │ │ └── together.md │ │ │ └── index.md │ │ ├── evaluation │ │ │ ├── _category_.yml │ │ │ ├── index.md │ │ │ └── modules │ │ │ │ ├── _category_.yml │ │ │ │ ├── correctness.md │ │ │ │ ├── faithfulness.md │ │ │ │ └── relevancy.md │ │ ├── ingestion_pipeline │ │ │ ├── _category_.yml │ │ │ ├── index.md │ │ │ └── transformations.md │ │ ├── llamacloud.mdx │ │ ├── llms │ │ │ ├── _category_.yml │ │ │ ├── available_llms │ │ │ │ ├── _category_.yml │ │ │ │ ├── anthropic.md │ │ │ │ ├── azure.md │ │ │ │ ├── fireworks.md │ │ │ │ ├── groq.mdx │ │ │ │ ├── llama2.md │ │ │ │ ├── mistral.md │ │ │ │ ├── ollama.md │ │ │ │ ├── openai.md │ │ │ │ ├── portkey.md │ │ │ │ └── together.md │ │ │ └── index.md │ │ ├── node_parser.md │ │ ├── node_postprocessors │ │ │ ├── _category_.yml │ │ │ ├── cohere_reranker.md │ │ │ └── index.md │ │ ├── prompt │ │ │ ├── _category_.yml │ │ │ └── index.md │ │ ├── query_engines │ │ │ ├── _category_.yml │ │ │ ├── index.md │ │ │ ├── metadata_filtering.md │ │ │ └── router_query_engine.md │ │ ├── response_synthesizer.md │ │ ├── retriever.md │ │ ├── storage.md │ │ └── vector_stores │ │ │ ├── _category_.yml │ │ │ └── qdrant.md │ │ └── observability │ │ ├── _category_.yml │ │ └── index.md ├── main.py ├── pipeline_storage │ ├── docstore.json │ └── llama_cache ├── poetry.lock ├── pyproject.toml └── tests │ └── __init__.py └── frontend ├── .gitignore ├── README.md ├── app ├── components │ ├── chat-section.tsx │ ├── header.tsx │ ├── transform.ts │ └── ui │ │ ├── README.md │ │ ├── button.tsx │ │ ├── chat │ │ ├── chat-actions.tsx │ │ ├── chat-avatar.tsx │ │ ├── chat-input.tsx │ │ ├── chat-message.tsx │ │ ├── chat-messages.tsx │ │ ├── chat.interface.ts │ │ ├── codeblock.tsx │ │ ├── index.ts │ │ ├── markdown.tsx │ │ └── use-copy-to-clipboard.tsx │ │ ├── input.tsx │ │ ├── lib │ │ └── utils.ts │ │ └── nodes.tsx ├── favicon.ico ├── globals.css ├── hooks │ └── useNodes.ts ├── layout.tsx └── page.tsx ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── llama.png ├── tailwind.config.ts └── tsconfig.json /README.md: -------------------------------------------------------------------------------- 1 | ### Advanced chatbot over LlamaIndex TS documentation 🔥 2 | 3 | https://github.com/rsrohan99/llamaindex-docs-agent/assets/62835870/42fbd1ba-c42f-4b86-b33e-093272d76639 4 | 5 | # Multi-document Agents 6 | 7 | This is a [LlamaIndex](https://www.llamaindex.ai/) project bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama). 8 | 9 | This multi-document agent is built over the LlamaIndex.TS documentation. 10 | 11 | We use our multi-document agent architecture: 12 | 13 | - Individual query engine per document 14 | - Top level Orchestrator agent across documents that can pick relevant subsets 15 | 16 | This also streams _all_ intermediate results from the agent via a custom Callback handler. 17 | 18 | We use this Custom Callback handler to also send intermediate nodes that are retrieved during retrieval of document level query engines, to the frontend. 19 | 20 | It allows us to show the relevant section of the documentation in the preview window. 21 | 22 | ## Main Files to Look At 23 | 24 | This extends beyond the simple `create-llama` example. To see changes, look at the following files: 25 | 26 | - `backend/app/utils/index.py` - contains core logic for constructing + getting multi-doc agent 27 | - `backend/app/api/routers/chat.py` - contains implementation of chat endpoint + threading to stream intermediate responses. 28 | 29 | We also created some custom `Transformations` that we use with out robust `IngestionPipeline` 30 | 31 | As we update the documentations in the `data` folder, this `IngestionPipeline` takes care of handling duplicates, applying our custom nodes transformation logic etc. 32 | 33 | The custom transformations we've used: 34 | 35 | - `Deduplicator` - handles duplicates. 36 | - `HyperlinksRemover` - cleans the markdown files. 37 | - `Summarizer` - creates summary of the node and adds that as a metadata. 38 | - `URLExtractor` - generates the url of a particular node section. 39 | - `Upserter` - updates the docstore with new and updated nodes, deletes old ones. 40 | 41 | ## Getting Started 42 | 43 | First, startup the backend as described in the [backend README](./backend/README.md). 44 | 45 | Second, run the development server of the frontend as described in the [frontend README](./frontend/README.md). 46 | 47 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 48 | 49 | ## Learn More 50 | 51 | To learn more about LlamaIndex, take a look at the following resources: 52 | 53 | - [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex (Python features). 54 | - [LlamaIndexTS Documentation](https://ts.llamaindex.ai) - learn about LlamaIndex (Typescript features). 55 | 56 | You can check out [the LlamaIndexTS GitHub repository](https://github.com/run-llama/LlamaIndexTS) - your feedback and contributions are welcome! 57 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | storage 3 | pipeline_storage -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | This is a [LlamaIndex](https://www.llamaindex.ai/) project using [FastAPI](https://fastapi.tiangolo.com/) bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama). 2 | 3 | ## Getting Started 4 | 5 | First, setup the environment: 6 | 7 | 8 | ``` 9 | poetry install 10 | poetry shell 11 | ``` 12 | 13 | By default, we use the OpenAI LLM (though you can customize, see app/api/routers/chat.py). As a result you need to specify an `OPENAI_API_KEY` in an .env file in this directory. 14 | 15 | Example `backend/.env` file: 16 | ``` 17 | OPENAI_API_KEY= 18 | ``` 19 | 20 | Second, run the development server: 21 | 22 | ``` 23 | python main.py 24 | ``` 25 | 26 | Then call the API endpoint `/api/chat` to see the result: 27 | 28 | ``` 29 | curl --location 'localhost:8000/api/chat' \ 30 | --header 'Content-Type: application/json' \ 31 | --data '{ "messages": [{ "role": "user", "content": "Hello" }] }' 32 | ``` 33 | 34 | You can start editing the API by modifying `app/api/routers/chat.py`. The endpoint auto-updates as you save the file. 35 | 36 | Open [http://localhost:8000/docs](http://localhost:8000/docs) with your browser to see the Swagger UI of the API. 37 | 38 | The API allows CORS for all origins to simplify development. You can change this behavior by setting the `ENVIRONMENT` environment variable to `prod`: 39 | 40 | ``` 41 | ENVIRONMENT=prod uvicorn main:app 42 | ``` 43 | 44 | ## Learn More 45 | 46 | To learn more about LlamaIndex, take a look at the following resources: 47 | 48 | - [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex. 49 | 50 | You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome! 51 | -------------------------------------------------------------------------------- /backend/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsrohan99/llamaindex-docs-agent/1b2a6f5efee1bd5a4b9cd7771eac5f76a57155b1/backend/app/__init__.py -------------------------------------------------------------------------------- /backend/app/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsrohan99/llamaindex-docs-agent/1b2a6f5efee1bd5a4b9cd7771eac5f76a57155b1/backend/app/api/__init__.py -------------------------------------------------------------------------------- /backend/app/api/routers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsrohan99/llamaindex-docs-agent/1b2a6f5efee1bd5a4b9cd7771eac5f76a57155b1/backend/app/api/routers/__init__.py -------------------------------------------------------------------------------- /backend/app/api/routers/chat.py: -------------------------------------------------------------------------------- 1 | from typing import List, cast 2 | 3 | from fastapi.responses import StreamingResponse 4 | 5 | from threading import Thread 6 | from app.utils.json import json_to_model 7 | from app.utils.index import EventObject, get_agent 8 | from fastapi import APIRouter, Depends, HTTPException, Request, status 9 | from llama_index.core.base.llms.types import MessageRole, ChatMessage 10 | from llama_index.agent.openai import OpenAIAgent 11 | from llama_index.core.chat_engine.types import StreamingAgentChatResponse 12 | from pydantic import BaseModel 13 | import logging 14 | import json 15 | 16 | chat_router = r = APIRouter() 17 | 18 | 19 | class _Message(BaseModel): 20 | role: MessageRole 21 | content: str 22 | 23 | 24 | class _ChatData(BaseModel): 25 | messages: List[_Message] 26 | 27 | 28 | def convert_sse(obj: str | dict): 29 | """Convert the given object (or string) to a Server-Sent Event (SSE) event""" 30 | # print(obj) 31 | return "data: {}\n\n".format(json.dumps(obj)) 32 | 33 | 34 | @r.post("") 35 | async def chat( 36 | request: Request, 37 | # Note: To support clients sending a JSON object using content-type "text/plain", 38 | # we need to use Depends(json_to_model(_ChatData)) here 39 | data: _ChatData = Depends(json_to_model(_ChatData)), 40 | agent: OpenAIAgent = Depends(get_agent), 41 | ): 42 | logger = logging.getLogger("uvicorn") 43 | # check preconditions and get last message 44 | if len(data.messages) == 0: 45 | raise HTTPException( 46 | status_code=status.HTTP_400_BAD_REQUEST, 47 | detail="No messages provided", 48 | ) 49 | lastMessage = data.messages.pop() 50 | if lastMessage.role != MessageRole.USER: 51 | raise HTTPException( 52 | status_code=status.HTTP_400_BAD_REQUEST, 53 | detail="Last message must be from user", 54 | ) 55 | # convert messages coming from the request to type ChatMessage 56 | messages = [ 57 | ChatMessage( 58 | role=m.role, 59 | content=m.content, 60 | ) 61 | for m in data.messages 62 | ] 63 | 64 | # start a new thread here to query chat engine 65 | thread = Thread(target=agent.stream_chat, args=(lastMessage.content, messages)) 66 | thread.start() 67 | logger.info("Querying chat engine") 68 | # response = agent.stream_chat(lastMessage.content, messages) 69 | # response = agent.chat(lastMessage.content, messages) 70 | # logger.info(response) 71 | 72 | # stream response 73 | # NOTE: changed to sync due to issues with blocking the event loop 74 | # see https://stackoverflow.com/a/75760884 75 | def event_generator(): 76 | queue = agent.callback_manager.handlers[0].queue 77 | 78 | # stream response 79 | while True: 80 | next_item = queue.get(True, 60.0) # set a generous timeout of 60 seconds 81 | # check type of next_item, if string or not 82 | if isinstance(next_item, EventObject): 83 | yield convert_sse(dict(next_item)) 84 | elif isinstance(next_item, StreamingAgentChatResponse): 85 | response = cast(StreamingAgentChatResponse, next_item) 86 | for token in response.response_gen: 87 | yield convert_sse(token) 88 | break 89 | 90 | return StreamingResponse(event_generator(), media_type="text/event-stream") 91 | -------------------------------------------------------------------------------- /backend/app/utils/Test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 5 6 | } 7 | -------------------------------------------------------------------------------- /backend/app/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsrohan99/llamaindex-docs-agent/1b2a6f5efee1bd5a4b9cd7771eac5f76a57155b1/backend/app/utils/__init__.py -------------------------------------------------------------------------------- /backend/app/utils/index.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from pydantic import BaseModel 4 | import shutil 5 | from queue import Queue 6 | 7 | from llama_index.agent.openai import OpenAIAgent 8 | from llama_index.core import VectorStoreIndex 9 | from llama_index.core.objects import ObjectIndex, SimpleToolNodeMapping 10 | from llama_index.core.tools import QueryEngineTool, ToolMetadata 11 | from llama_index.llms.openai import OpenAI 12 | from llama_index.embeddings.openai import OpenAIEmbedding 13 | from llama_index.readers.file import FlatReader 14 | from llama_index.core.callbacks import CallbackManager 15 | from llama_index.core.callbacks.base_handler import BaseCallbackHandler 16 | from llama_index.core.callbacks.schema import CBEventType 17 | from llama_index.core.readers import SimpleDirectoryReader 18 | from llama_index.core.storage.docstore import SimpleDocumentStore 19 | from llama_index.core.ingestion import IngestionPipeline 20 | from llama_index.core.schema import TextNode 21 | from llama_index.core.settings import Settings 22 | from llama_index.core.indices import load_index_from_storage 23 | from llama_index.core.storage import StorageContext 24 | from llama_index.core.storage.docstore.types import BaseDocumentStore 25 | from llama_index.core.callbacks.schema import EventPayload 26 | from llama_index.core.schema import NodeWithScore 27 | 28 | from app.utils.node_parsers.markdown import CustomMarkdownNodeParser 29 | from app.utils.transformations import URLExtractor, Deduplicator, Upserter 30 | from app.utils.transformations import HyperlinksRemover, DocsSummarizer 31 | from app.utils.misc import get_max_h_value 32 | 33 | from typing import Optional, Dict, Any, List, Tuple 34 | 35 | import os 36 | 37 | 38 | PIPELINE_STORAGE_DIR = "./pipeline_storage" # directory to cache the ingestion pipeline 39 | STORAGE_DIR = "./storage" 40 | DATA_DIR = "./data" # directory containing the documents to index 41 | 42 | 43 | class EventObject(BaseModel): 44 | """ 45 | Represents an event from the LlamaIndex callback handler. 46 | 47 | Attributes: 48 | type (str): The type of the event, e.g. "function_call". 49 | payload (dict): The payload associated with the event. 50 | """ 51 | 52 | type: str 53 | payload: dict 54 | 55 | 56 | class StreamingCallbackHandler(BaseCallbackHandler): 57 | """Callback handler specifically designed to stream function calls to a queue.""" 58 | 59 | def __init__(self, queue: Queue) -> None: 60 | """Initialize the base callback handler.""" 61 | super().__init__([], []) 62 | self._queue = queue 63 | self._counter = 0 64 | 65 | def on_event_start( 66 | self, 67 | event_type: CBEventType, 68 | payload: Optional[Dict[str, Any]] = None, 69 | event_id: str = "", 70 | parent_id: str = "", 71 | **kwargs: Any, 72 | ) -> str: 73 | """Run when an event starts and return id of event.""" 74 | if event_type == CBEventType.FUNCTION_CALL: 75 | self._queue.put( 76 | EventObject( 77 | type="function_call", 78 | payload={ 79 | "arguments_str": payload["function_call"], 80 | "tool_str": payload["tool"].name, 81 | }, 82 | ) 83 | ) 84 | 85 | def on_event_end( 86 | self, 87 | event_type: CBEventType, 88 | payload: Optional[Dict[str, Any]] = None, 89 | event_id: str = "", 90 | **kwargs: Any, 91 | ) -> None: 92 | # print(event_type) 93 | # print(payload) 94 | """Run when an event ends.""" 95 | # if event_type == CBEventType.FUNCTION_CALL: 96 | # # print(payload) 97 | # self._queue.put( 98 | # EventObject( 99 | # type="function_call_response", 100 | # payload={"response": payload["function_call_response"]}, 101 | # ) 102 | # ) 103 | if event_type == CBEventType.AGENT_STEP: 104 | # put LLM response into queue 105 | self._queue.put(payload["response"]) 106 | elif event_type == CBEventType.RETRIEVE: 107 | nodes_with_scores: list[NodeWithScore] = payload[EventPayload.NODES] 108 | nodes_to_return = [] 109 | for node_with_score in nodes_with_scores: 110 | node = node_with_score.node 111 | node_meta = node.metadata 112 | # print(node_meta) 113 | if 'section_link' in node_meta: 114 | nodes_to_return.append({ 115 | "id": node.id_, 116 | "title": get_max_h_value(node_meta) 117 | or node_meta['file_path'], 118 | "url": node.metadata['file_path'], 119 | "section": node.metadata['section_link'], 120 | "summary": node.metadata['summary'], 121 | }) 122 | # print(nodes_to_return) 123 | self._queue.put( 124 | EventObject( 125 | type="nodes_retrieved", 126 | payload={ 127 | "nodes": nodes_to_return 128 | } 129 | ) 130 | ) 131 | 132 | @property 133 | def queue(self) -> Queue: 134 | """Get the queue of events.""" 135 | return self._queue 136 | 137 | @property 138 | def counter(self) -> int: 139 | """Get the counter.""" 140 | return self._counter 141 | 142 | def start_trace(self, trace_id: Optional[str] = None) -> None: 143 | """Run when an overall trace is launched.""" 144 | pass 145 | 146 | def end_trace( 147 | self, 148 | trace_id: Optional[str] = None, 149 | trace_map: Optional[Dict[str, List[str]]] = None, 150 | ) -> None: 151 | """Run when an overall trace is exited.""" 152 | pass 153 | 154 | 155 | def clean_old_persisted_indices(doc_id:str): 156 | dir_to_delete = f"{STORAGE_DIR}" 157 | if os.path.exists(dir_to_delete): 158 | print(f"Deleting dir: {dir_to_delete}") 159 | shutil.rmtree(dir_to_delete) 160 | 161 | async def ingest(directory:str, docstore: BaseDocumentStore)->TextNode: 162 | reader = SimpleDirectoryReader( 163 | input_dir=directory, 164 | recursive=True, 165 | required_exts=[".md", ".mdx"], 166 | file_extractor={ 167 | ".md": FlatReader(), 168 | ".mdx": FlatReader() 169 | } 170 | ) 171 | 172 | docs = reader.load_data() 173 | 174 | deduplicator = Deduplicator( 175 | cleanup_fn=clean_old_persisted_indices, 176 | docstore=docstore, 177 | ) 178 | url_extractor = URLExtractor(data_path=DATA_DIR) 179 | hyperlinks_remover = HyperlinksRemover() 180 | summarizer = DocsSummarizer( 181 | llm="gpt-3.5-turbo-0125" 182 | ) 183 | upserter = Upserter( 184 | docstore=docstore, 185 | persist_dir=PIPELINE_STORAGE_DIR 186 | ) 187 | 188 | pipeline = IngestionPipeline( 189 | transformations=[ 190 | deduplicator, 191 | hyperlinks_remover, 192 | summarizer, 193 | url_extractor, 194 | upserter 195 | ], 196 | ) 197 | 198 | if os.path.exists(PIPELINE_STORAGE_DIR): 199 | pipeline.load(PIPELINE_STORAGE_DIR) 200 | nodes = await pipeline.arun(documents=docs) 201 | else: 202 | nodes = await pipeline.arun(documents=docs) 203 | pipeline.persist(PIPELINE_STORAGE_DIR) 204 | 205 | print(f"new nodes: {len(nodes)}") 206 | if len(nodes) > 0: 207 | # print(nodes) 208 | docstore.add_documents(nodes) 209 | docstore.persist( 210 | persist_path=os.path.join(PIPELINE_STORAGE_DIR, "docstore.json") 211 | ) 212 | # if os.path.exists(STORAGE_DIR): 213 | # print(f"Deleting {STORAGE_DIR}") 214 | # shutil.rmtree(STORAGE_DIR) 215 | all_doc_ids = docstore.get_all_document_hashes().values() 216 | # print(all_doc_ids) 217 | return [docstore.get_document(doc_id=id) for id in all_doc_ids] 218 | 219 | 220 | def _build_document_agents( 221 | storage_dir: str, docs: list[TextNode], callback_manager: CallbackManager 222 | ) -> Dict: 223 | """Build document agents.""" 224 | node_parser = CustomMarkdownNodeParser() 225 | llm = OpenAI(temperature=0, model="gpt-3.5-turbo-0125") 226 | embed_model = OpenAIEmbedding( 227 | model="text-embedding-3-small" 228 | ) 229 | Settings.llm = llm 230 | Settings.embed_model = embed_model 231 | # Settings.callback_manager = callback_manager 232 | # service_context = ServiceContext.from_defaults(llm=llm) 233 | 234 | # Build agents dictionary 235 | agents = {} 236 | 237 | # this is for the baseline 238 | all_nodes = [] 239 | 240 | for idx, doc in enumerate(docs): 241 | nodes = node_parser.get_nodes_from_documents([doc]) 242 | all_nodes.extend(nodes) 243 | 244 | if not os.path.exists(f"./{storage_dir}/{doc.id_}"): 245 | # build vector index 246 | vector_index = VectorStoreIndex( 247 | nodes, 248 | ) 249 | vector_index.storage_context.persist( 250 | persist_dir=f"./{storage_dir}/{doc.id_}" 251 | ) 252 | else: 253 | vector_index = load_index_from_storage( 254 | StorageContext.from_defaults( 255 | persist_dir=f"./{storage_dir}/{doc.id_}" 256 | ), 257 | ) 258 | 259 | # define query engines 260 | vector_index._callback_manager = callback_manager 261 | vector_query_engine = vector_index.as_query_engine() 262 | agents[doc.id_] = vector_query_engine 263 | 264 | return agents 265 | 266 | 267 | def _build_top_agent( 268 | storage_dir: str, doc_agents: Dict, docstore: BaseDocumentStore, 269 | callback_manager: CallbackManager 270 | ) -> OpenAIAgent: 271 | """Build top-level agent.""" 272 | # define tool for each document agent 273 | all_tools = [] 274 | for doc_id in doc_agents.keys(): 275 | doc = docstore.get_document(doc_id=doc_id) 276 | assert doc is not None 277 | wiki_summary = ( 278 | f"This is the brief summary of one LlamaIndex documentation page: \"{doc.metadata['summary']}\". Use" 279 | f" this tool if you want to answer any questions about topics from the above summary. Please ask this tool a clearly specified elaborate query while using it and fully utilize it's response to answer the final query. Example input to this tool -> input: \"install llamaindex using node js?\". Don't use 1-2 word queries\n" 280 | ) 281 | doc_tool = QueryEngineTool( 282 | query_engine=doc_agents[doc_id], 283 | metadata=ToolMetadata( 284 | name=f"page_{doc.metadata['filename'].split('.')[0]}", 285 | description=wiki_summary, 286 | ), 287 | ) 288 | all_tools.append(doc_tool) 289 | tool_mapping = SimpleToolNodeMapping.from_objects(all_tools) 290 | # if obj_index doesn't already exist 291 | if not os.path.exists(f"./{storage_dir}/top"): 292 | storage_context = StorageContext.from_defaults() 293 | obj_index = ObjectIndex.from_objects( 294 | all_tools, tool_mapping, VectorStoreIndex, storage_context=storage_context 295 | ) 296 | storage_context.persist(persist_dir=f"./{storage_dir}/top") 297 | # TODO: don't access private property 298 | 299 | else: 300 | # initialize storage context from existing storage 301 | storage_context = StorageContext.from_defaults( 302 | persist_dir=f"./{storage_dir}/top" 303 | ) 304 | index = load_index_from_storage(storage_context) 305 | obj_index = ObjectIndex(index, tool_mapping) 306 | 307 | top_agent = OpenAIAgent.from_tools( 308 | tool_retriever=obj_index.as_retriever(similarity_top_k=7), 309 | system_prompt=""" \ 310 | You are an agent designed to answer queries about a Generative AI framework, LlamaIndex. 311 | Please always use the tools provided to answer a question. Do not rely on prior knowledge. Pass the provided tools with clear and elaborate queries (e.g. "install llamaindex using node js?") and then fully utilize their response to answer the original query. When using multiple tools, break the original query into multiple elaborate queries and pass them to the respective tool as input. Be sure to make the inputs long and elaborate to capture entire context of the query. Don't use 1-2 word queries.\ 312 | 313 | """, 314 | verbose=True, 315 | callback_manager=callback_manager, 316 | ) 317 | 318 | return top_agent 319 | 320 | 321 | async def get_agent(): 322 | logger = logging.getLogger("uvicorn") 323 | 324 | if os.path.exists(PIPELINE_STORAGE_DIR): 325 | docstore = SimpleDocumentStore.from_persist_dir(PIPELINE_STORAGE_DIR) 326 | else: 327 | docstore = SimpleDocumentStore() 328 | 329 | docs = await ingest( 330 | # directory=f'{DATA_DIR}/docs/modules/ingestion_pipeline', 331 | # directory=f'{DATA_DIR}/docs/getting_started', 332 | directory=f'{DATA_DIR}/docs/modules', 333 | docstore=docstore, 334 | ) 335 | 336 | # define callback manager with streaming 337 | queue = Queue() 338 | handler = StreamingCallbackHandler(queue) 339 | callback_manager = CallbackManager([handler]) 340 | 341 | # build agent for each document 342 | doc_agents = _build_document_agents( 343 | STORAGE_DIR, docs, callback_manager=callback_manager 344 | ) 345 | 346 | # build top-level agent 347 | top_agent = _build_top_agent( 348 | STORAGE_DIR, doc_agents, docstore, callback_manager 349 | ) 350 | 351 | logger.info(f"Built agent.") 352 | 353 | return top_agent 354 | -------------------------------------------------------------------------------- /backend/app/utils/json.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import TypeVar 3 | from fastapi import HTTPException, Request 4 | 5 | from pydantic import BaseModel, ValidationError 6 | 7 | 8 | T = TypeVar("T", bound=BaseModel) 9 | 10 | 11 | def json_to_model(cls: T): 12 | async def get_json(request: Request) -> T: 13 | body = await request.body() 14 | try: 15 | data_dict = json.loads(body.decode("utf-8")) 16 | return cls(**data_dict) 17 | except (json.JSONDecodeError, ValidationError) as e: 18 | raise HTTPException( 19 | status_code=400, detail=f"Could not decode JSON: {str(e)}" 20 | ) 21 | 22 | return get_json 23 | -------------------------------------------------------------------------------- /backend/app/utils/misc.py: -------------------------------------------------------------------------------- 1 | def get_max_h_value(dic): 2 | h_keys = [ 3 | key for key in dic.keys() 4 | if key.startswith('Header ') and key.split(' ')[-1].isdigit() 5 | ] 6 | if not h_keys: 7 | return None # No keys with 'Header ' pattern found 8 | max_key = max(h_keys, key=lambda k: int(k.split(' ')[-1])) 9 | return dic[max_key] -------------------------------------------------------------------------------- /backend/app/utils/node_parsers/markdown.py: -------------------------------------------------------------------------------- 1 | """Custom Markdown node parser.""" 2 | import re 3 | from llama_index.core.node_parser import MarkdownNodeParser 4 | from llama_index.core.node_parser.node_utils import build_nodes_from_splits 5 | from llama_index.core.schema import BaseNode, TextNode 6 | from llama_index.readers.file import MarkdownReader 7 | 8 | def generate_markdown_header_id(header_text, previous_ids=None): 9 | if previous_ids is None: 10 | previous_ids = set() 11 | 12 | # Step 1: Convert to lowercase 13 | id = header_text.lower() 14 | 15 | # Step 2: Remove non-word text 16 | id = re.sub(r'\W+', ' ', id) 17 | 18 | # Step 3: Convert spaces to hyphens 19 | id = id.strip().replace(' ', '-') 20 | 21 | # Step 4: Remove multiple consecutive hyphens 22 | id = re.sub(r'-+', '-', id) 23 | 24 | # Step 5 & 6: Ensure uniqueness 25 | original_id = id 26 | counter = 1 27 | while id in previous_ids: 28 | id = f"{original_id}-{counter}" 29 | counter += 1 30 | 31 | # Add the new unique id to the set of previous ids 32 | # previous_ids.add(id) 33 | 34 | return id 35 | 36 | class CustomMarkdownNodeParser(MarkdownNodeParser): 37 | """Markdown node parser. 38 | 39 | Splits a document into Nodes using custom Markdown splitting logic. 40 | 41 | Args: 42 | include_metadata (bool): whether to include metadata in nodes 43 | include_prev_next_rel (bool): whether to include prev/next relationships 44 | 45 | """ 46 | 47 | def _update_metadata( 48 | self, headers_metadata: dict, new_header: str, new_header_level: int 49 | ) -> dict: 50 | """Update the markdown headers for metadata. 51 | 52 | Removes all headers that are equal or less than the level 53 | of the newly found header 54 | """ 55 | updated_headers = {} 56 | 57 | for i in range(1, new_header_level): 58 | key = f"Header {i}" 59 | if key in headers_metadata: 60 | updated_headers[key] = headers_metadata[key] 61 | 62 | updated_headers[f"Header {new_header_level}"] = new_header 63 | # print(updated_headers) 64 | updated_headers['section_link'] \ 65 | = generate_markdown_header_id(new_header, updated_headers.values()) 66 | return updated_headers 67 | 68 | def _build_node_from_split( 69 | self, 70 | text_split: str, 71 | node: BaseNode, 72 | metadata: dict, 73 | ) -> TextNode: 74 | """Build node from single text split.""" 75 | md_reader = MarkdownReader() 76 | 77 | node = build_nodes_from_splits([text_split], node, id_func=self.id_func)[0] 78 | 79 | node.text = md_reader.remove_hyperlinks(node.text) 80 | node.text = md_reader.remove_images(node.text) 81 | 82 | if self.include_metadata: 83 | node.metadata = {**node.metadata, **metadata} 84 | 85 | return node -------------------------------------------------------------------------------- /backend/app/utils/test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | 5 | from llama_index.core.readers import SimpleDirectoryReader 6 | from llama_index.readers.file import FlatReader 7 | from llama_index.core.schema import MetadataMode 8 | from llama_index.core.ingestion import IngestionPipeline 9 | from llama_index.core.storage.docstore import SimpleDocumentStore 10 | 11 | from app.utils.node_parsers.markdown import CustomMarkdownNodeParser 12 | from app.utils.transformations import URLExtractor, Deduplicator, Upserter 13 | from app.utils.transformations import HyperlinksRemover, DocsSummarizer 14 | 15 | load_dotenv() 16 | 17 | DATA_PATH="./data" 18 | PIPELINE_STORAGE_PATH="./pipeline_storage" 19 | def main(): 20 | reader = SimpleDirectoryReader( 21 | input_dir=f'{DATA_PATH}/docs/getting_started', 22 | required_exts=[".md", ".mdx"], 23 | file_extractor={ 24 | ".md": FlatReader(), 25 | ".mdx": FlatReader() 26 | } 27 | ) 28 | 29 | docs = reader.load_data() 30 | # for doc in docs: 31 | # doc.metadata["url_path"], _ = splitext(relpath( 32 | # doc.metadata['file_path'], DATA_PATH+"/docs" 33 | # )) 34 | 35 | 36 | print(len(docs)) 37 | # print(docs[1]) 38 | if os.path.exists(PIPELINE_STORAGE_PATH): 39 | docstore = SimpleDocumentStore.from_persist_dir(PIPELINE_STORAGE_PATH) 40 | else: 41 | docstore = SimpleDocumentStore() 42 | 43 | deduplicator = Deduplicator( 44 | docstore=docstore, 45 | ) 46 | hyperlinks_remover = HyperlinksRemover() 47 | url_extractor = URLExtractor(data_path=DATA_PATH) 48 | summarizer = DocsSummarizer( 49 | llm="gpt-3.5-turbo-0125" 50 | ) 51 | upserter = Upserter( 52 | docstore=docstore, 53 | persist_dir=PIPELINE_STORAGE_PATH 54 | ) 55 | node_parser = CustomMarkdownNodeParser() 56 | 57 | pipeline = IngestionPipeline( 58 | transformations=[ 59 | deduplicator, 60 | hyperlinks_remover, 61 | summarizer, 62 | url_extractor, 63 | upserter 64 | ], 65 | ) 66 | 67 | if os.path.exists(PIPELINE_STORAGE_PATH): 68 | pipeline.load(PIPELINE_STORAGE_PATH) 69 | pipeline.run(documents=docs) 70 | else: 71 | pipeline.run(documents=docs) 72 | pipeline.persist(PIPELINE_STORAGE_PATH) 73 | 74 | 75 | # nodes = node_parser.get_nodes_from_documents(docs) 76 | all_doc_ids = docstore.get_all_document_hashes().values() 77 | print(all_doc_ids) 78 | all_docs = [docstore.get_document(doc_id=id) for id in docstore.get_all_document_hashes().values()] 79 | # print(all_docs.get_content(metadata_mode=MetadataMode.ALL)) 80 | 81 | print(len(all_docs)) 82 | # print(nodes[2:6]) 83 | # print(all_docs[2].get_content(metadata_mode=MetadataMode.ALL)) 84 | print(all_docs[2].metadata) -------------------------------------------------------------------------------- /backend/app/utils/transformations/__init__.py: -------------------------------------------------------------------------------- 1 | """Custom Transformations.""" 2 | 3 | from app.utils.transformations.deduplicator import Deduplicator 4 | from app.utils.transformations.url_extractor import URLExtractor 5 | from app.utils.transformations.upserter import Upserter 6 | from app.utils.transformations.hyperlinks_remover import HyperlinksRemover 7 | from app.utils.transformations.summarizer import DocsSummarizer 8 | 9 | __all__ = [ 10 | "Deduplicator", 11 | "URLExtractor", 12 | "Upserter", 13 | "DocsSummarizer", 14 | "HyperlinksRemover", 15 | ] -------------------------------------------------------------------------------- /backend/app/utils/transformations/deduplicator.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from llama_index.core.schema import TransformComponent 4 | from llama_index.core.storage.docstore.types import BaseDocumentStore 5 | from llama_index.core.bridge.pydantic import Field 6 | 7 | class Deduplicator(TransformComponent): 8 | """Deduplicate documents and also delete old and updated ones.""" 9 | 10 | docstore: BaseDocumentStore = Field( 11 | description='Document store to check for duplicates' 12 | ) 13 | 14 | cleanup_fn: Callable = Field( 15 | default=lambda _:..., 16 | description="after deleting missing nodes, call this function." 17 | ) 18 | 19 | def __call__(self, nodes, **kwargs): 20 | assert self.docstore is not None 21 | 22 | existing_doc_ids_before = set( 23 | self.docstore.get_all_document_hashes().values() 24 | ) 25 | doc_ids_from_nodes = set() 26 | deduped_nodes_to_run = {} 27 | 28 | for node in nodes: 29 | ref_doc_id = node.ref_doc_id if node.ref_doc_id else node.id_ 30 | doc_ids_from_nodes.add(ref_doc_id) 31 | existing_hash = self.docstore.get_document_hash(ref_doc_id) 32 | if not existing_hash: 33 | # document doesn't exist, so add it 34 | print(f"new document {ref_doc_id}") 35 | self.docstore.set_document_hash(ref_doc_id, node.hash) 36 | deduped_nodes_to_run[ref_doc_id] = node 37 | elif existing_hash and existing_hash != node.hash: 38 | print(f"updated document {ref_doc_id}") 39 | self.docstore.delete_ref_doc(ref_doc_id, raise_error=False) 40 | self.docstore.set_document_hash(ref_doc_id, node.hash) 41 | deduped_nodes_to_run[ref_doc_id] = node 42 | self.cleanup_fn(ref_doc_id) 43 | else: 44 | print(f"skipping document {ref_doc_id}") 45 | continue # document exists and is unchanged, so skip it 46 | 47 | doc_ids_to_delete = existing_doc_ids_before - doc_ids_from_nodes 48 | for ref_doc_id in doc_ids_to_delete: 49 | print(f"deleting missing document {ref_doc_id}") 50 | self.docstore.delete_document(ref_doc_id) 51 | self.cleanup_fn(ref_doc_id) 52 | 53 | nodes_to_return = list(deduped_nodes_to_run.values()) 54 | if len(nodes_to_return) > 0: 55 | self.cleanup_fn(ref_doc_id) 56 | 57 | 58 | return nodes_to_return -------------------------------------------------------------------------------- /backend/app/utils/transformations/hyperlinks_remover.py: -------------------------------------------------------------------------------- 1 | from llama_index.core.schema import TransformComponent 2 | from llama_index.core.bridge.pydantic import Field 3 | from llama_index.readers.file import MarkdownReader 4 | 5 | class HyperlinksRemover(TransformComponent): 6 | """Remove hyperlinks and images from md or mdx.""" 7 | 8 | def __call__(self, nodes, **kwargs): 9 | md_reader = MarkdownReader() 10 | for node in nodes: 11 | node.text = md_reader.remove_hyperlinks(node.text) 12 | node.text = md_reader.remove_images(node.text) 13 | # print(node.metadata) 14 | return nodes -------------------------------------------------------------------------------- /backend/app/utils/transformations/summarizer.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from llama_index.core.schema import TransformComponent 4 | from llama_index.core.bridge.pydantic import Field 5 | from llama_index.core.response_synthesizers import TreeSummarize 6 | from llama_index.llms.openai import OpenAI 7 | 8 | class DocsSummarizer(TransformComponent): 9 | """Summarize current documentation page.""" 10 | 11 | llm: str = Field( 12 | default='gpt-3.5-turbo', 13 | description='LLM to summarize' 14 | ) 15 | 16 | async def generate_summary(self, node, summarizer, prompt): 17 | print(f"getting summary for {node.id_}") 18 | summary = await summarizer.aget_response(prompt, [node.text]) 19 | node.metadata['summary'] = summary 20 | 21 | async def process_nodes(self, nodes, summarizer, prompt): 22 | tasks = [] 23 | for node in nodes: 24 | task = asyncio.create_task( 25 | self.generate_summary(node, summarizer, prompt) 26 | ) 27 | tasks.append(task) 28 | await asyncio.gather(*tasks) 29 | 30 | 31 | def __call__(self, nodes, **kwargs): 32 | print('calling') 33 | 34 | async def acall(self, nodes, **kwargs): 35 | summarizer = TreeSummarize( 36 | verbose=True, 37 | llm=OpenAI( 38 | model=self.llm, 39 | temperature=0, 40 | ) 41 | ) 42 | 43 | SUMMARY_PROMPT = "Give me a brief summary under 50 words of the given LlamaIndex documentation page. There are many pages, this is just one of them. This 50-word summary must cover everything discussed in this particular documentation page but briefly so that someone reading this brief summary will get a complete picture of what they'll learn if they read the entire page." 44 | 45 | # loop = asyncio.get_event_loop() 46 | # loop.run_until_complete( 47 | # self.process_nodes(nodes, summarizer, SUMMARY_PROMPT) 48 | # ) 49 | await self.process_nodes(nodes, summarizer, SUMMARY_PROMPT) 50 | return nodes -------------------------------------------------------------------------------- /backend/app/utils/transformations/upserter.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from llama_index.core.schema import TransformComponent 4 | from llama_index.core.bridge.pydantic import Field 5 | from llama_index.core.storage.docstore.types import BaseDocumentStore 6 | 7 | class Upserter(TransformComponent): 8 | """Extract the relative path or a documentation page.""" 9 | 10 | docstore: BaseDocumentStore = Field( 11 | description='Document store to check for duplicates' 12 | ) 13 | 14 | persist_dir: str = Field( 15 | description='persist path for docstore' 16 | ) 17 | 18 | def __call__(self, nodes, **kwargs): 19 | assert self.docstore is not None 20 | 21 | self.docstore.add_documents(nodes) 22 | self.docstore.persist( 23 | persist_path=os.path.join(self.persist_dir, "docstore.json") 24 | ) 25 | return nodes -------------------------------------------------------------------------------- /backend/app/utils/transformations/url_extractor.py: -------------------------------------------------------------------------------- 1 | from os.path import relpath, splitext 2 | 3 | from llama_index.core.schema import TransformComponent 4 | from llama_index.core.bridge.pydantic import Field 5 | 6 | class URLExtractor(TransformComponent): 7 | """Upsert the new docs to the docstore.""" 8 | 9 | data_path: str = Field( 10 | default='./data', 11 | description='Relative Data directory' 12 | ) 13 | 14 | def __call__(self, nodes, **kwargs): 15 | for node in nodes: 16 | node.metadata["file_path"], _ = splitext(relpath( 17 | node.metadata['file_path'], self.data_path+"/docs" 18 | )) 19 | # print(node.metadata) 20 | return nodes -------------------------------------------------------------------------------- /backend/app/utils/vectorstores/dummy.py: -------------------------------------------------------------------------------- 1 | from llama_index.core.vector_stores.types import BasePydanticVectorStore 2 | 3 | class DummyVectorStore(BasePydanticVectorStore): 4 | stores_text = False 5 | def client(self): 6 | ... 7 | 8 | def add(self, _): 9 | ... 10 | 11 | def delete(self, _, **__): 12 | ... 13 | 14 | def query(self, _, **__): 15 | ... -------------------------------------------------------------------------------- /backend/data/docs/.gitignore: -------------------------------------------------------------------------------- 1 | api/ 2 | -------------------------------------------------------------------------------- /backend/data/docs/_static/concepts/indexing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsrohan99/llamaindex-docs-agent/1b2a6f5efee1bd5a4b9cd7771eac5f76a57155b1/backend/data/docs/_static/concepts/indexing.jpg -------------------------------------------------------------------------------- /backend/data/docs/_static/concepts/querying.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsrohan99/llamaindex-docs-agent/1b2a6f5efee1bd5a4b9cd7771eac5f76a57155b1/backend/data/docs/_static/concepts/querying.jpg -------------------------------------------------------------------------------- /backend/data/docs/_static/concepts/rag.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsrohan99/llamaindex-docs-agent/1b2a6f5efee1bd5a4b9cd7771eac5f76a57155b1/backend/data/docs/_static/concepts/rag.jpg -------------------------------------------------------------------------------- /backend/data/docs/getting_started/_category_.yml: -------------------------------------------------------------------------------- 1 | label: Getting Started 2 | position: 1 3 | -------------------------------------------------------------------------------- /backend/data/docs/getting_started/concepts.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Concepts 6 | 7 | LlamaIndex.TS helps you build LLM-powered applications (e.g. Q&A, chatbot) over custom data. 8 | 9 | In this high-level concepts guide, you will learn: 10 | 11 | - how an LLM can answer questions using your own data. 12 | - key concepts and modules in LlamaIndex.TS for composing your own query pipeline. 13 | 14 | ## Answering Questions Across Your Data 15 | 16 | LlamaIndex uses a two stage method when using an LLM with your data: 17 | 18 | 1. **indexing stage**: preparing a knowledge base, and 19 | 2. **querying stage**: retrieving relevant context from the knowledge to assist the LLM in responding to a question 20 | 21 | ![](../_static/concepts/rag.jpg) 22 | 23 | This process is also known as Retrieval Augmented Generation (RAG). 24 | 25 | LlamaIndex.TS provides the essential toolkit for making both steps super easy. 26 | 27 | Let's explore each stage in detail. 28 | 29 | ### Indexing Stage 30 | 31 | LlamaIndex.TS help you prepare the knowledge base with a suite of data connectors and indexes. 32 | 33 | ![](../_static/concepts/indexing.jpg) 34 | 35 | [**Data Loaders**](../modules/data_loader.md): 36 | A data connector (i.e. `Reader`) ingest data from different data sources and data formats into a simple `Document` representation (text and simple metadata). 37 | 38 | [**Documents / Nodes**](../modules/documents_and_nodes/index.md): A `Document` is a generic container around any data source - for instance, a PDF, an API output, or retrieved data from a database. A `Node` is the atomic unit of data in LlamaIndex and represents a "chunk" of a source `Document`. It's a rich representation that includes metadata and relationships (to other nodes) to enable accurate and expressive retrieval operations. 39 | 40 | [**Data Indexes**](../modules/data_index.md): 41 | Once you've ingested your data, LlamaIndex helps you index data into a format that's easy to retrieve. 42 | 43 | Under the hood, LlamaIndex parses the raw documents into intermediate representations, calculates vector embeddings, and stores your data in-memory or to disk. 44 | 45 | ### Querying Stage 46 | 47 | In the querying stage, the query pipeline retrieves the most relevant context given a user query, 48 | and pass that to the LLM (along with the query) to synthesize a response. 49 | 50 | This gives the LLM up-to-date knowledge that is not in its original training data, 51 | (also reducing hallucination). 52 | 53 | The key challenge in the querying stage is retrieval, orchestration, and reasoning over (potentially many) knowledge bases. 54 | 55 | LlamaIndex provides composable modules that help you build and integrate RAG pipelines for Q&A (query engine), chatbot (chat engine), or as part of an agent. 56 | 57 | These building blocks can be customized to reflect ranking preferences, as well as composed to reason over multiple knowledge bases in a structured way. 58 | 59 | ![](../_static/concepts/querying.jpg) 60 | 61 | #### Building Blocks 62 | 63 | [**Retrievers**](../modules/retriever.md): 64 | A retriever defines how to efficiently retrieve relevant context from a knowledge base (i.e. index) when given a query. 65 | The specific retrieval logic differs for difference indices, the most popular being dense retrieval against a vector index. 66 | 67 | [**Response Synthesizers**](../modules/response_synthesizer.md): 68 | A response synthesizer generates a response from an LLM, using a user query and a given set of retrieved text chunks. 69 | 70 | #### Pipelines 71 | 72 | [**Query Engines**](../modules/query_engines): 73 | A query engine is an end-to-end pipeline that allow you to ask question over your data. 74 | It takes in a natural language query, and returns a response, along with reference context retrieved and passed to the LLM. 75 | 76 | [**Chat Engines**](../modules/chat_engine.md): 77 | A chat engine is an end-to-end pipeline for having a conversation with your data 78 | (multiple back-and-forth instead of a single question & answer). 79 | -------------------------------------------------------------------------------- /backend/data/docs/getting_started/environments.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Environments 6 | 7 | LlamaIndex currently officially supports NodeJS 18 and NodeJS 20. 8 | 9 | ## NextJS App Router 10 | 11 | If you're using NextJS App Router route handlers/serverless functions, you'll need to use the NodeJS mode: 12 | 13 | ```js 14 | export const runtime = "nodejs"; // default 15 | ``` 16 | -------------------------------------------------------------------------------- /backend/data/docs/getting_started/installation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | --- 4 | 5 | # Installation and Setup 6 | 7 | Make sure you have NodeJS v18 or higher. 8 | 9 | ## Using create-llama 10 | 11 | The easiest way to get started with LlamaIndex is by using `create-llama`. This CLI tool enables you to quickly start building a new LlamaIndex application, with everything set up for you. 12 | 13 | Just run 14 | 15 | 16 | 17 | 18 | ```bash 19 | npx create-llama@latest 20 | ``` 21 | 22 | 23 | 24 | 25 | ```bash 26 | yarn create llama 27 | ``` 28 | 29 | 30 | 31 | 32 | ```bash 33 | pnpm create llama@latest 34 | ``` 35 | 36 | 37 | 38 | 39 | to get started. Once your app is generated, run 40 | 41 | ```bash npm2yarn 42 | npm run dev 43 | ``` 44 | 45 | to start the development server. You can then visit [http://localhost:3000](http://localhost:3000) to see your app 46 | 47 | ## Installation from NPM 48 | 49 | ```bash npm2yarn 50 | npm install llamaindex 51 | ``` 52 | 53 | ### Environment variables 54 | 55 | Our examples use OpenAI by default. You'll need to set up your Open AI key like so: 56 | 57 | ```bash 58 | export OPENAI_API_KEY="sk-......" # Replace with your key from https://platform.openai.com/account/api-keys 59 | ``` 60 | 61 | If you want to have it automatically loaded every time, add it to your `.zshrc/.bashrc`. 62 | 63 | WARNING: do not check in your OpenAI key into version control. 64 | -------------------------------------------------------------------------------- /backend/data/docs/getting_started/starter.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | import CodeBlock from "@theme/CodeBlock"; 6 | import CodeSource from "!raw-loader!../../../../examples/vectorIndex"; 7 | import TSConfigSource from "!!raw-loader!../../../../examples/tsconfig.json"; 8 | 9 | # Starter Tutorial 10 | 11 | Make sure you have installed LlamaIndex.TS and have an OpenAI key. If you haven't, check out the [installation](installation) guide. 12 | 13 | ## From scratch(node.js + TypeScript): 14 | 15 | In a new folder: 16 | 17 | ```bash npm2yarn 18 | npm init 19 | npm install -D typescript @types/node 20 | ``` 21 | 22 | Create the file `example.ts`. This code will load some example data, create a document, index it (which creates embeddings using OpenAI), and then creates query engine to answer questions about the data. 23 | 24 | {CodeSource} 25 | 26 | Create a `tsconfig.json` file in the same folder: 27 | 28 | {TSConfigSource} 29 | 30 | Now you can run the code with 31 | 32 | ```bash 33 | npx tsx example.ts 34 | ``` 35 | 36 | Also, you can clone our examples and try them out: 37 | 38 | ```bash npm2yarn 39 | npx degit run-llama/LlamaIndexTS/examples my-new-project 40 | cd my-new-project 41 | npm install 42 | npx tsx ./vectorIndex.ts 43 | ``` 44 | 45 | ## From scratch (Next.js + TypeScript): 46 | 47 | You just need one command to create a new Next.js project: 48 | 49 | ```bash npm2yarn 50 | npx create-llama@latest 51 | ``` 52 | -------------------------------------------------------------------------------- /backend/data/docs/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | slug: / 4 | --- 5 | 6 | # What is LlamaIndex.TS? 7 | 8 | LlamaIndex.TS is a data framework for LLM applications to ingest, structure, and access private or domain-specific data. While a python package is also available (see [here](https://docs.llamaindex.ai/en/stable/)), LlamaIndex.TS offers core features in a simple package, optimized for usage with TypeScript. 9 | 10 | ## 🚀 Why LlamaIndex.TS? 11 | 12 | At their core, LLMs offer a natural language interface between humans and inferred data. Widely available models come pre-trained on huge amounts of publicly available data, from Wikipedia and mailing lists to textbooks and source code. 13 | 14 | Applications built on top of LLMs often require augmenting these models with private or domain-specific data. Unfortunately, that data can be distributed across siloed applications and data stores. It's behind APIs, in SQL databases, or trapped in PDFs and slide decks. 15 | 16 | That's where **LlamaIndex.TS** comes in. 17 | 18 | ## 🦙 How can LlamaIndex.TS help? 19 | 20 | LlamaIndex.TS provides the following tools: 21 | 22 | - **Data loading** ingest your existing `.txt`, `.pdf`, `.csv`, `.md` and `.docx` data directly 23 | - **Data indexes** structure your data in intermediate representations that are easy and performant for LLMs to consume. 24 | - **Engines** provide natural language access to your data. For example: 25 | - Query engines are powerful retrieval interfaces for knowledge-augmented output. 26 | - Chat engines are conversational interfaces for multi-message, "back and forth" interactions with your data. 27 | 28 | ## 👨‍👩‍👧‍👦 Who is LlamaIndex for? 29 | 30 | LlamaIndex.TS provides a core set of tools, essential for anyone building LLM apps with JavaScript and TypeScript. 31 | 32 | Our high-level API allows beginner users to use LlamaIndex.TS to ingest and query their data. 33 | 34 | For more complex applications, our lower-level APIs allow advanced users to customize and extend any module—data connectors, indices, retrievers, and query engines, to fit their needs. 35 | 36 | ## Getting Started 37 | 38 | `npm install llamaindex` 39 | 40 | Our documentation includes [Installation Instructions](./getting_started/installation.mdx) and a [Starter Tutorial](./getting_started/starter.mdx) to build your first application. 41 | 42 | Once you're up and running, [High-Level Concepts](./getting_started/concepts.md) has an overview of LlamaIndex's modular architecture. For more hands-on practical examples, look through our Examples section on the sidebar. 43 | 44 | ## 🗺️ Ecosystem 45 | 46 | To download or contribute, find LlamaIndex on: 47 | 48 | - Github: https://github.com/run-llama/LlamaIndexTS 49 | - NPM: https://www.npmjs.com/package/llamaindex 50 | 51 | ## Community 52 | 53 | Need help? Have a feature suggestion? Join the LlamaIndex community: 54 | 55 | - Twitter: https://twitter.com/llama_index 56 | - Discord https://discord.gg/dGcwcsnxhU 57 | -------------------------------------------------------------------------------- /backend/data/docs/modules/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Modules" 2 | collapsed: false 3 | position: 5 4 | -------------------------------------------------------------------------------- /backend/data/docs/modules/agent/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Agents" 2 | position: 3 3 | -------------------------------------------------------------------------------- /backend/data/docs/modules/agent/index.md: -------------------------------------------------------------------------------- 1 | # Agents 2 | 3 | An “agent” is an automated reasoning and decision engine. It takes in a user input/query and can make internal decisions for executing that query in order to return the correct result. The key agent components can include, but are not limited to: 4 | 5 | - Breaking down a complex question into smaller ones 6 | - Choosing an external Tool to use + coming up with parameters for calling the Tool 7 | - Planning out a set of tasks 8 | - Storing previously completed tasks in a memory module 9 | 10 | ## Getting Started 11 | 12 | LlamaIndex.TS comes with a few built-in agents, but you can also create your own. The built-in agents include: 13 | 14 | - [OpenAI Agent](./openai.mdx) 15 | -------------------------------------------------------------------------------- /backend/data/docs/modules/agent/multi_document_agent.mdx: -------------------------------------------------------------------------------- 1 | # Multi-Document Agent 2 | 3 | In this guide, you learn towards setting up an agent that can effectively answer different types of questions over a larger set of documents. 4 | 5 | These questions include the following 6 | 7 | - QA over a specific doc 8 | - QA comparing different docs 9 | - Summaries over a specific doc 10 | - Comparing summaries between different docs 11 | 12 | We do this with the following architecture: 13 | 14 | - setup a “document agent” over each Document: each doc agent can do QA/summarization within its doc 15 | - setup a top-level agent over this set of document agents. Do tool retrieval and then do CoT over the set of tools to answer a question. 16 | 17 | ## Setup and Download Data 18 | 19 | We first start by installing the necessary libraries and downloading the data. 20 | 21 | ```bash 22 | pnpm i llamaindex 23 | ``` 24 | 25 | ```ts 26 | import { 27 | Document, 28 | ObjectIndex, 29 | OpenAI, 30 | OpenAIAgent, 31 | QueryEngineTool, 32 | SimpleNodeParser, 33 | SimpleToolNodeMapping, 34 | SummaryIndex, 35 | VectorStoreIndex, 36 | serviceContextFromDefaults, 37 | storageContextFromDefaults, 38 | } from "llamaindex"; 39 | ``` 40 | 41 | And then for the data we will run through a list of countries and download the wikipedia page for each country. 42 | 43 | ```ts 44 | import fs from "fs"; 45 | import path from "path"; 46 | 47 | const dataPath = path.join(__dirname, "tmp_data"); 48 | 49 | const extractWikipediaTitle = async (title: string) => { 50 | const fileExists = fs.existsSync(path.join(dataPath, `${title}.txt`)); 51 | 52 | if (fileExists) { 53 | console.log(`File already exists for the title: ${title}`); 54 | return; 55 | } 56 | 57 | const queryParams = new URLSearchParams({ 58 | action: "query", 59 | format: "json", 60 | titles: title, 61 | prop: "extracts", 62 | explaintext: "true", 63 | }); 64 | 65 | const url = `https://en.wikipedia.org/w/api.php?${queryParams}`; 66 | 67 | const response = await fetch(url); 68 | const data: any = await response.json(); 69 | 70 | const pages = data.query.pages; 71 | const page = pages[Object.keys(pages)[0]]; 72 | const wikiText = page.extract; 73 | 74 | await new Promise((resolve) => { 75 | fs.writeFile(path.join(dataPath, `${title}.txt`), wikiText, (err: any) => { 76 | if (err) { 77 | console.error(err); 78 | resolve(title); 79 | return; 80 | } 81 | console.log(`${title} stored in file!`); 82 | 83 | resolve(title); 84 | }); 85 | }); 86 | }; 87 | ``` 88 | 89 | ```ts 90 | export const extractWikipedia = async (titles: string[]) => { 91 | if (!fs.existsSync(dataPath)) { 92 | fs.mkdirSync(dataPath); 93 | } 94 | 95 | for await (const title of titles) { 96 | await extractWikipediaTitle(title); 97 | } 98 | 99 | console.log("Extration finished!"); 100 | ``` 101 | 102 | These files will be saved in the `tmp_data` folder. 103 | 104 | Now we can call the function to download the data for each country. 105 | 106 | ```ts 107 | await extractWikipedia([ 108 | "Brazil", 109 | "United States", 110 | "Canada", 111 | "Mexico", 112 | "Argentina", 113 | "Chile", 114 | "Colombia", 115 | "Peru", 116 | "Venezuela", 117 | "Ecuador", 118 | "Bolivia", 119 | "Paraguay", 120 | "Uruguay", 121 | "Guyana", 122 | "Suriname", 123 | "French Guiana", 124 | "Falkland Islands", 125 | ]); 126 | ``` 127 | 128 | ## Load the data 129 | 130 | Now that we have the data, we can load it into the LlamaIndex and store as a document. 131 | 132 | ```ts 133 | import { Document } from "llamaindex"; 134 | 135 | const countryDocs: Record = {}; 136 | 137 | for (const title of wikiTitles) { 138 | const path = `./agent/helpers/tmp_data/${title}.txt`; 139 | const text = await fs.readFile(path, "utf-8"); 140 | const document = new Document({ text: text, id_: path }); 141 | countryDocs[title] = document; 142 | } 143 | ``` 144 | 145 | ## Setup LLM and StorageContext 146 | 147 | We will be using gpt-4 for this example and we will use the `StorageContext` to store the documents in-memory. 148 | 149 | ```ts 150 | const llm = new OpenAI({ 151 | model: "gpt-4", 152 | }); 153 | 154 | const ctx = serviceContextFromDefaults({ llm }); 155 | 156 | const storageContext = await storageContextFromDefaults({ 157 | persistDir: "./storage", 158 | }); 159 | ``` 160 | 161 | ## Building Multi-Document Agents 162 | 163 | In this section we show you how to construct the multi-document agent. We first build a document agent for each document, and then define the top-level parent agent with an object index. 164 | 165 | ```ts 166 | const documentAgents: Record = {}; 167 | const queryEngines: Record = {}; 168 | ``` 169 | 170 | Now we iterate over each country and create a document agent for each one. 171 | 172 | ### Build Agent for each Document 173 | 174 | In this section we define “document agents” for each document. 175 | 176 | We define both a vector index (for semantic search) and summary index (for summarization) for each document. The two query engines are then converted into tools that are passed to an OpenAI function calling agent. 177 | 178 | This document agent can dynamically choose to perform semantic search or summarization within a given document. 179 | 180 | We create a separate document agent for each coutnry. 181 | 182 | ```ts 183 | for (const title of wikiTitles) { 184 | // parse the document into nodes 185 | const nodes = new SimpleNodeParser({ 186 | chunkSize: 200, 187 | chunkOverlap: 20, 188 | }).getNodesFromDocuments([countryDocs[title]]); 189 | 190 | // create the vector index for specific search 191 | const vectorIndex = await VectorStoreIndex.init({ 192 | serviceContext: serviceContext, 193 | storageContext: storageContext, 194 | nodes, 195 | }); 196 | 197 | // create the summary index for broader search 198 | const summaryIndex = await SummaryIndex.init({ 199 | serviceContext: serviceContext, 200 | nodes, 201 | }); 202 | 203 | const vectorQueryEngine = summaryIndex.asQueryEngine(); 204 | const summaryQueryEngine = summaryIndex.asQueryEngine(); 205 | 206 | // create the query engines for each task 207 | const queryEngineTools = [ 208 | new QueryEngineTool({ 209 | queryEngine: vectorQueryEngine, 210 | metadata: { 211 | name: "vector_tool", 212 | description: `Useful for questions related to specific aspects of ${title} (e.g. the history, arts and culture, sports, demographics, or more).`, 213 | }, 214 | }), 215 | new QueryEngineTool({ 216 | queryEngine: summaryQueryEngine, 217 | metadata: { 218 | name: "summary_tool", 219 | description: `Useful for any requests that require a holistic summary of EVERYTHING about ${title}. For questions about more specific sections, please use the vector_tool.`, 220 | }, 221 | }), 222 | ]; 223 | 224 | // create the document agent 225 | const agent = new OpenAIAgent({ 226 | tools: queryEngineTools, 227 | llm, 228 | verbose: true, 229 | }); 230 | 231 | documentAgents[title] = agent; 232 | queryEngines[title] = vectorIndex.asQueryEngine(); 233 | } 234 | ``` 235 | 236 | ## Build Top-Level Agent 237 | 238 | Now we define the top-level agent that can answer questions over the set of document agents. 239 | 240 | This agent takes in all document agents as tools. This specific agent RetrieverOpenAIAgent performs tool retrieval before tool use (unlike a default agent that tries to put all tools in the prompt). 241 | 242 | Here we use a top-k retriever, but we encourage you to customize the tool retriever method! 243 | 244 | Firstly, we create a tool for each document agent 245 | 246 | ```ts 247 | const allTools: QueryEngineTool[] = []; 248 | ``` 249 | 250 | ```ts 251 | for (const title of wikiTitles) { 252 | const wikiSummary = ` 253 | This content contains Wikipedia articles about ${title}. 254 | Use this tool if you want to answer any questions about ${title} 255 | `; 256 | 257 | const docTool = new QueryEngineTool({ 258 | queryEngine: documentAgents[title], 259 | metadata: { 260 | name: `tool_${title}`, 261 | description: wikiSummary, 262 | }, 263 | }); 264 | 265 | allTools.push(docTool); 266 | } 267 | ``` 268 | 269 | Our top level agent will use this document agents as tools and use toolRetriever to retrieve the best tool to answer a question. 270 | 271 | ```ts 272 | // map the tools to nodes 273 | const toolMapping = SimpleToolNodeMapping.fromObjects(allTools); 274 | 275 | // create the object index 276 | const objectIndex = await ObjectIndex.fromObjects( 277 | allTools, 278 | toolMapping, 279 | VectorStoreIndex, 280 | { 281 | serviceContext, 282 | storageContext, 283 | }, 284 | ); 285 | 286 | // create the top agent 287 | const topAgent = new OpenAIAgent({ 288 | toolRetriever: await objectIndex.asRetriever({}), 289 | llm, 290 | verbose: true, 291 | prefixMessages: [ 292 | { 293 | content: 294 | "You are an agent designed to answer queries about a set of given countries. Please always use the tools provided to answer a question. Do not rely on prior knowledge.", 295 | role: "system", 296 | }, 297 | ], 298 | }); 299 | ``` 300 | 301 | ## Use the Agent 302 | 303 | Now we can use the agent to answer questions. 304 | 305 | ```ts 306 | const response = await topAgent.chat({ 307 | message: "Tell me the differences between Brazil and Canada economics?", 308 | }); 309 | 310 | // print output 311 | console.log(response); 312 | ``` 313 | 314 | You can find the full code for this example [here](https://github.com/run-llama/LlamaIndexTS/tree/main/examples/agent/multi-document-agent.ts) 315 | -------------------------------------------------------------------------------- /backend/data/docs/modules/agent/openai.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | --- 4 | 5 | # OpenAI Agent 6 | 7 | OpenAI API that supports function calling, it’s never been easier to build your own agent! 8 | 9 | In this notebook tutorial, we showcase how to write your own OpenAI agent 10 | 11 | ## Setup 12 | 13 | First, you need to install the `llamaindex` package. You can do this by running the following command in your terminal: 14 | 15 | ```bash 16 | pnpm i llamaindex 17 | ``` 18 | 19 | Then we can define a function to sum two numbers and another function to divide two numbers. 20 | 21 | ```ts 22 | function sumNumbers({ a, b }: { a: number; b: number }): number { 23 | return a + b; 24 | } 25 | 26 | // Define a function to divide two numbers 27 | function divideNumbers({ a, b }: { a: number; b: number }): number { 28 | return a / b; 29 | } 30 | ``` 31 | 32 | ## Create a function tool 33 | 34 | Now we can create a function tool from the sum function and another function tool from the divide function. 35 | 36 | For the parameters of the sum function, we can define a JSON schema. 37 | 38 | ### JSON Schema 39 | 40 | ```ts 41 | const sumJSON = { 42 | type: "object", 43 | properties: { 44 | a: { 45 | type: "number", 46 | description: "The first number", 47 | }, 48 | b: { 49 | type: "number", 50 | description: "The second number", 51 | }, 52 | }, 53 | required: ["a", "b"], 54 | }; 55 | 56 | const divideJSON = { 57 | type: "object", 58 | properties: { 59 | a: { 60 | type: "number", 61 | description: "The dividend a to divide", 62 | }, 63 | b: { 64 | type: "number", 65 | description: "The divisor b to divide by", 66 | }, 67 | }, 68 | required: ["a", "b"], 69 | }; 70 | 71 | const sumFunctionTool = new FunctionTool(sumNumbers, { 72 | name: "sumNumbers", 73 | description: "Use this function to sum two numbers", 74 | parameters: sumJSON, 75 | }); 76 | 77 | const divideFunctionTool = new FunctionTool(divideNumbers, { 78 | name: "divideNumbers", 79 | description: "Use this function to divide two numbers", 80 | parameters: divideJSON, 81 | }); 82 | ``` 83 | 84 | ## Create an OpenAIAgent 85 | 86 | Now we can create an OpenAIAgent with the function tools. 87 | 88 | ```ts 89 | const agent = new OpenAIAgent({ 90 | tools: [sumFunctionTool, divideFunctionTool], 91 | verbose: true, 92 | }); 93 | ``` 94 | 95 | ## Chat with the agent 96 | 97 | Now we can chat with the agent. 98 | 99 | ```ts 100 | const response = await agent.chat({ 101 | message: "How much is 5 + 5? then divide by 2", 102 | }); 103 | 104 | console.log(String(response)); 105 | ``` 106 | 107 | ## Full code 108 | 109 | ```ts 110 | import { FunctionTool, OpenAIAgent } from "llamaindex"; 111 | 112 | // Define a function to sum two numbers 113 | function sumNumbers({ a, b }: { a: number; b: number }): number { 114 | return a + b; 115 | } 116 | 117 | // Define a function to divide two numbers 118 | function divideNumbers({ a, b }: { a: number; b: number }): number { 119 | return a / b; 120 | } 121 | 122 | // Define the parameters of the sum function as a JSON schema 123 | const sumJSON = { 124 | type: "object", 125 | properties: { 126 | a: { 127 | type: "number", 128 | description: "The first number", 129 | }, 130 | b: { 131 | type: "number", 132 | description: "The second number", 133 | }, 134 | }, 135 | required: ["a", "b"], 136 | }; 137 | 138 | // Define the parameters of the divide function as a JSON schema 139 | const divideJSON = { 140 | type: "object", 141 | properties: { 142 | a: { 143 | type: "number", 144 | description: "The argument a to divide", 145 | }, 146 | b: { 147 | type: "number", 148 | description: "The argument b to divide", 149 | }, 150 | }, 151 | required: ["a", "b"], 152 | }; 153 | 154 | async function main() { 155 | // Create a function tool from the sum function 156 | const sumFunctionTool = new FunctionTool(sumNumbers, { 157 | name: "sumNumbers", 158 | description: "Use this function to sum two numbers", 159 | parameters: sumJSON, 160 | }); 161 | 162 | // Create a function tool from the divide function 163 | const divideFunctionTool = new FunctionTool(divideNumbers, { 164 | name: "divideNumbers", 165 | description: "Use this function to divide two numbers", 166 | parameters: divideJSON, 167 | }); 168 | 169 | // Create an OpenAIAgent with the function tools 170 | const agent = new OpenAIAgent({ 171 | tools: [sumFunctionTool, divideFunctionTool], 172 | verbose: true, 173 | }); 174 | 175 | // Chat with the agent 176 | const response = await agent.chat({ 177 | message: "How much is 5 + 5? then divide by 2", 178 | }); 179 | 180 | // Print the response 181 | console.log(String(response)); 182 | } 183 | 184 | main().then(() => { 185 | console.log("Done"); 186 | }); 187 | ``` 188 | -------------------------------------------------------------------------------- /backend/data/docs/modules/agent/query_engine_tool.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # OpenAI Agent + QueryEngineTool 6 | 7 | QueryEngineTool is a tool that allows you to query a vector index. In this example, we will create a vector index from a set of documents and then create a QueryEngineTool from the vector index. We will then create an OpenAIAgent with the QueryEngineTool and chat with the agent. 8 | 9 | ## Setup 10 | 11 | First, you need to install the `llamaindex` package. You can do this by running the following command in your terminal: 12 | 13 | ```bash 14 | pnpm i llamaindex 15 | ``` 16 | 17 | Then you can import the necessary classes and functions. 18 | 19 | ```ts 20 | import { 21 | OpenAIAgent, 22 | SimpleDirectoryReader, 23 | VectorStoreIndex, 24 | QueryEngineTool, 25 | } from "llamaindex"; 26 | ``` 27 | 28 | ## Create a vector index 29 | 30 | Now we can create a vector index from a set of documents. 31 | 32 | ```ts 33 | // Load the documents 34 | const documents = await new SimpleDirectoryReader().loadData({ 35 | directoryPath: "node_modules/llamaindex/examples/", 36 | }); 37 | 38 | // Create a vector index from the documents 39 | const vectorIndex = await VectorStoreIndex.fromDocuments(documents); 40 | ``` 41 | 42 | ## Create a QueryEngineTool 43 | 44 | Now we can create a QueryEngineTool from the vector index. 45 | 46 | ```ts 47 | // Create a query engine from the vector index 48 | const abramovQueryEngine = vectorIndex.asQueryEngine(); 49 | 50 | // Create a QueryEngineTool with the query engine 51 | const queryEngineTool = new QueryEngineTool({ 52 | queryEngine: abramovQueryEngine, 53 | metadata: { 54 | name: "abramov_query_engine", 55 | description: "A query engine for the Abramov documents", 56 | }, 57 | }); 58 | ``` 59 | 60 | ## Create an OpenAIAgent 61 | 62 | ```ts 63 | // Create an OpenAIAgent with the query engine tool tools 64 | 65 | const agent = new OpenAIAgent({ 66 | tools: [queryEngineTool], 67 | verbose: true, 68 | }); 69 | ``` 70 | 71 | ## Chat with the agent 72 | 73 | Now we can chat with the agent. 74 | 75 | ```ts 76 | const response = await agent.chat({ 77 | message: "What was his salary?", 78 | }); 79 | 80 | console.log(String(response)); 81 | ``` 82 | 83 | ## Full code 84 | 85 | ```ts 86 | import { 87 | OpenAIAgent, 88 | SimpleDirectoryReader, 89 | VectorStoreIndex, 90 | QueryEngineTool, 91 | } from "llamaindex"; 92 | 93 | async function main() { 94 | // Load the documents 95 | const documents = await new SimpleDirectoryReader().loadData({ 96 | directoryPath: "node_modules/llamaindex/examples/", 97 | }); 98 | 99 | // Create a vector index from the documents 100 | const vectorIndex = await VectorStoreIndex.fromDocuments(documents); 101 | 102 | // Create a query engine from the vector index 103 | const abramovQueryEngine = vectorIndex.asQueryEngine(); 104 | 105 | // Create a QueryEngineTool with the query engine 106 | const queryEngineTool = new QueryEngineTool({ 107 | queryEngine: abramovQueryEngine, 108 | metadata: { 109 | name: "abramov_query_engine", 110 | description: "A query engine for the Abramov documents", 111 | }, 112 | }); 113 | 114 | // Create an OpenAIAgent with the function tools 115 | const agent = new OpenAIAgent({ 116 | tools: [queryEngineTool], 117 | verbose: true, 118 | }); 119 | 120 | // Chat with the agent 121 | const response = await agent.chat({ 122 | message: "What was his salary?", 123 | }); 124 | 125 | // Print the response 126 | console.log(String(response)); 127 | } 128 | 129 | main().then(() => { 130 | console.log("Done"); 131 | }); 132 | ``` 133 | -------------------------------------------------------------------------------- /backend/data/docs/modules/agent/react_agent.mdx: -------------------------------------------------------------------------------- 1 | # ReAct Agent 2 | 3 | The ReAct agent is an AI agent that can reason over the next action, construct an action command, execute the action, and repeat these steps in an iterative loop until the task is complete. 4 | 5 | In this notebook tutorial, we showcase how to write your ReAct agent using the `llamaindex` package. 6 | 7 | ## Setup 8 | 9 | First, you need to install the `llamaindex` package. You can do this by running the following command in your terminal: 10 | 11 | ```bash 12 | pnpm i llamaindex 13 | ``` 14 | 15 | And then you can import the `OpenAIAgent` and `FunctionTool` from the `llamaindex` package. 16 | 17 | ```ts 18 | import { FunctionTool, OpenAIAgent } from "llamaindex"; 19 | ``` 20 | 21 | Then we can define a function to sum two numbers and another function to divide two numbers. 22 | 23 | ```ts 24 | function sumNumbers({ a, b }: { a: number; b: number }): number { 25 | return a + b; 26 | } 27 | 28 | // Define a function to divide two numbers 29 | function divideNumbers({ a, b }: { a: number; b: number }): number { 30 | return a / b; 31 | } 32 | ``` 33 | 34 | ## Create a function tool 35 | 36 | Now we can create a function tool from the sum function and another function tool from the divide function. 37 | 38 | For the parameters of the sum function, we can define a JSON schema. 39 | 40 | ### JSON Schema 41 | 42 | ```ts 43 | const sumJSON = { 44 | type: "object", 45 | properties: { 46 | a: { 47 | type: "number", 48 | description: "The first number", 49 | }, 50 | b: { 51 | type: "number", 52 | description: "The second number", 53 | }, 54 | }, 55 | required: ["a", "b"], 56 | }; 57 | 58 | const divideJSON = { 59 | type: "object", 60 | properties: { 61 | a: { 62 | type: "number", 63 | description: "The dividend a to divide", 64 | }, 65 | b: { 66 | type: "number", 67 | description: "The divisor b to divide by", 68 | }, 69 | }, 70 | required: ["a", "b"], 71 | }; 72 | 73 | const sumFunctionTool = new FunctionTool(sumNumbers, { 74 | name: "sumNumbers", 75 | description: "Use this function to sum two numbers", 76 | parameters: sumJSON, 77 | }); 78 | 79 | const divideFunctionTool = new FunctionTool(divideNumbers, { 80 | name: "divideNumbers", 81 | description: "Use this function to divide two numbers", 82 | parameters: divideJSON, 83 | }); 84 | ``` 85 | 86 | ## Create an ReAct 87 | 88 | Now we can create an OpenAIAgent with the function tools. 89 | 90 | ```ts 91 | const agent = new ReActAgent({ 92 | tools: [sumFunctionTool, divideFunctionTool], 93 | verbose: true, 94 | }); 95 | ``` 96 | 97 | ## Chat with the agent 98 | 99 | Now we can chat with the agent. 100 | 101 | ```ts 102 | const response = await agent.chat({ 103 | message: "How much is 5 + 5? then divide by 2", 104 | }); 105 | 106 | console.log(String(response)); 107 | ``` 108 | 109 | The output will be: 110 | 111 | ```bash 112 | Thought: I need to use a tool to help me answer the question. 113 | Action: sumNumbers 114 | Action Input: {"a":5,"b":5} 115 | 116 | Observation: 10 117 | Thought: I can answer without using any more tools. 118 | Answer: The sum of 5 and 5 is 10, and when divided by 2, the result is 5. 119 | 120 | The sum of 5 and 5 is 10, and when divided by 2, the result is 5. 121 | ``` 122 | 123 | ## Full code 124 | 125 | ```ts 126 | import { FunctionTool, ReActAgent } from "llamaindex"; 127 | 128 | // Define a function to sum two numbers 129 | function sumNumbers({ a, b }: { a: number; b: number }): number { 130 | return a + b; 131 | } 132 | 133 | // Define a function to divide two numbers 134 | function divideNumbers({ a, b }: { a: number; b: number }): number { 135 | return a / b; 136 | } 137 | 138 | // Define the parameters of the sum function as a JSON schema 139 | const sumJSON = { 140 | type: "object", 141 | properties: { 142 | a: { 143 | type: "number", 144 | description: "The first number", 145 | }, 146 | b: { 147 | type: "number", 148 | description: "The second number", 149 | }, 150 | }, 151 | required: ["a", "b"], 152 | }; 153 | 154 | // Define the parameters of the divide function as a JSON schema 155 | const divideJSON = { 156 | type: "object", 157 | properties: { 158 | a: { 159 | type: "number", 160 | description: "The argument a to divide", 161 | }, 162 | b: { 163 | type: "number", 164 | description: "The argument b to divide", 165 | }, 166 | }, 167 | required: ["a", "b"], 168 | }; 169 | 170 | async function main() { 171 | // Create a function tool from the sum function 172 | const sumFunctionTool = new FunctionTool(sumNumbers, { 173 | name: "sumNumbers", 174 | description: "Use this function to sum two numbers", 175 | parameters: sumJSON, 176 | }); 177 | 178 | // Create a function tool from the divide function 179 | const divideFunctionTool = new FunctionTool(divideNumbers, { 180 | name: "divideNumbers", 181 | description: "Use this function to divide two numbers", 182 | parameters: divideJSON, 183 | }); 184 | 185 | // Create an OpenAIAgent with the function tools 186 | const agent = new OpenAIAgent({ 187 | tools: [sumFunctionTool, divideFunctionTool], 188 | verbose: true, 189 | }); 190 | 191 | // Chat with the agent 192 | const response = await agent.chat({ 193 | message: "I want to sum 5 and 5 and then divide by 2", 194 | }); 195 | 196 | // Print the response 197 | console.log(String(response)); 198 | } 199 | 200 | main().then(() => { 201 | console.log("Done"); 202 | }); 203 | ``` 204 | -------------------------------------------------------------------------------- /backend/data/docs/modules/chat_engine.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # ChatEngine 6 | 7 | The chat engine is a quick and simple way to chat with the data in your index. 8 | 9 | ```typescript 10 | const retriever = index.asRetriever(); 11 | const chatEngine = new ContextChatEngine({ retriever }); 12 | 13 | // start chatting 14 | const response = await chatEngine.chat({ message: query }); 15 | ``` 16 | 17 | The `chat` function also supports streaming, just add `stream: true` as an option: 18 | 19 | ```typescript 20 | const stream = await chatEngine.chat({ message: query, stream: true }); 21 | for await (const chunk of stream) { 22 | process.stdout.write(chunk.response); 23 | } 24 | ``` 25 | 26 | ## Api References 27 | 28 | - [ContextChatEngine](../api/classes/ContextChatEngine.md) 29 | - [CondenseQuestionChatEngine](../api/classes/ContextChatEngine.md) 30 | -------------------------------------------------------------------------------- /backend/data/docs/modules/data_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Index 6 | 7 | An index is the basic container and organization for your data. LlamaIndex.TS supports two indexes: 8 | 9 | - `VectorStoreIndex` - will send the top-k `Node`s to the LLM when generating a response. The default top-k is 2. 10 | - `SummaryIndex` - will send every `Node` in the index to the LLM in order to generate a response 11 | 12 | ```typescript 13 | import { Document, VectorStoreIndex } from "llamaindex"; 14 | 15 | const document = new Document({ text: "test" }); 16 | 17 | const index = await VectorStoreIndex.fromDocuments([document]); 18 | ``` 19 | 20 | ## API Reference 21 | 22 | - [SummaryIndex](../api/classes/SummaryIndex.md) 23 | - [VectorStoreIndex](../api/classes/VectorStoreIndex.md) 24 | -------------------------------------------------------------------------------- /backend/data/docs/modules/data_loader.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | import CodeBlock from "@theme/CodeBlock"; 6 | import CodeSource from "!raw-loader!../../../../examples/readers/src/simple-directory-reader"; 7 | import CodeSource2 from "!raw-loader!../../../../examples/readers/src/custom-simple-directory-reader"; 8 | import CodeSource3 from "!raw-loader!../../../../examples/readers/src/llamaparse"; 9 | 10 | # Loader 11 | 12 | Before you can start indexing your documents, you need to load them into memory. 13 | 14 | ### SimpleDirectoryReader 15 | 16 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/run-llama/LlamaIndexTS/tree/main/examples/readers?file=src/simple-directory-reader.ts&title=Simple%20Directory%20Reader) 17 | 18 | LlamaIndex.TS supports easy loading of files from folders using the `SimpleDirectoryReader` class. 19 | 20 | It is a simple reader that reads all files from a directory and its subdirectories. 21 | 22 | {CodeSource} 23 | 24 | Currently, it supports reading `.csv`, `.docx`, `.html`, `.md` and `.pdf` files, 25 | but support for other file types is planned. 26 | 27 | Also, you can provide a `defaultReader` as a fallback for files with unsupported extensions. 28 | Or pass new readers for `fileExtToReader` to support more file types. 29 | 30 | 31 | {CodeSource2} 32 | 33 | 34 | ### LlamaParse 35 | 36 | LlamaParse is an API created by LlamaIndex to efficiently parse files, e.g. it's great at converting PDF tables into markdown. 37 | 38 | To use it, first login and get an API key from https://cloud.llamaindex.ai. Make sure to store the key in the environment variable `LLAMA_CLOUD_API_KEY`. 39 | 40 | Then, you can use the `LlamaParseReader` class to read a local PDF file and convert it into a markdown document that can be used by LlamaIndex: 41 | 42 | {CodeSource3} 43 | 44 | Alternatively, you can set the [`resultType`](../api/classes/LlamaParseReader.md#resulttype) option to `text` to get the parsed document as a text string. 45 | 46 | ## API Reference 47 | 48 | - [SimpleDirectoryReader](../api/classes/SimpleDirectoryReader.md) 49 | -------------------------------------------------------------------------------- /backend/data/docs/modules/documents_and_nodes/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Document / Nodes" 2 | position: 0 3 | -------------------------------------------------------------------------------- /backend/data/docs/modules/documents_and_nodes/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Documents and Nodes 6 | 7 | `Document`s and `Node`s are the basic building blocks of any index. While the API for these objects is similar, `Document` objects represent entire files, while `Node`s are smaller pieces of that original document, that are suitable for an LLM and Q&A. 8 | 9 | ```typescript 10 | import { Document } from "llamaindex"; 11 | 12 | document = new Document({ text: "text", metadata: { key: "val" } }); 13 | ``` 14 | 15 | ## API Reference 16 | 17 | - [Document](../api/classes/Document.md) 18 | - [TextNode](../api/classes/TextNode.md) 19 | -------------------------------------------------------------------------------- /backend/data/docs/modules/documents_and_nodes/metadata_extraction.md: -------------------------------------------------------------------------------- 1 | # Metadata Extraction Usage Pattern 2 | 3 | You can use LLMs to automate metadata extraction with our `Metadata Extractor` modules. 4 | 5 | Our metadata extractor modules include the following "feature extractors": 6 | 7 | - `SummaryExtractor` - automatically extracts a summary over a set of Nodes 8 | - `QuestionsAnsweredExtractor` - extracts a set of questions that each Node can answer 9 | - `TitleExtractor` - extracts a title over the context of each Node by document and combine them 10 | - `KeywordExtractor` - extracts keywords over the context of each Node 11 | 12 | Then you can chain the `Metadata Extractors` with the `IngestionPipeline` to extract metadata from a set of documents. 13 | 14 | ```ts 15 | import { 16 | IngestionPipeline, 17 | TitleExtractor, 18 | QuestionsAnsweredExtractor, 19 | Document, 20 | OpenAI, 21 | } from "llamaindex"; 22 | 23 | async function main() { 24 | const pipeline = new IngestionPipeline({ 25 | transformations: [ 26 | new TitleExtractor(), 27 | new QuestionsAnsweredExtractor({ 28 | questions: 5, 29 | }), 30 | ], 31 | }); 32 | 33 | const nodes = await pipeline.run({ 34 | documents: [ 35 | new Document({ text: "I am 10 years old. John is 20 years old." }), 36 | ], 37 | }); 38 | 39 | for (const node of nodes) { 40 | console.log(node.metadata); 41 | } 42 | } 43 | 44 | main().then(() => console.log("done")); 45 | ``` 46 | -------------------------------------------------------------------------------- /backend/data/docs/modules/embeddings/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Embeddings" 2 | position: 3 3 | -------------------------------------------------------------------------------- /backend/data/docs/modules/embeddings/available_embeddings/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Available Embeddings" 2 | -------------------------------------------------------------------------------- /backend/data/docs/modules/embeddings/available_embeddings/huggingface.md: -------------------------------------------------------------------------------- 1 | # HuggingFace 2 | 3 | To use HuggingFace embeddings, you need to import `HuggingFaceEmbedding` from `llamaindex`. 4 | 5 | ```ts 6 | import { HuggingFaceEmbedding, serviceContextFromDefaults } from "llamaindex"; 7 | 8 | const huggingFaceEmbeds = new HuggingFaceEmbedding(); 9 | 10 | const serviceContext = serviceContextFromDefaults({ embedModel: openaiEmbeds }); 11 | 12 | const document = new Document({ text: essay, id_: "essay" }); 13 | 14 | const index = await VectorStoreIndex.fromDocuments([document], { 15 | serviceContext, 16 | }); 17 | 18 | const queryEngine = index.asQueryEngine(); 19 | 20 | const query = "What is the meaning of life?"; 21 | 22 | const results = await queryEngine.query({ 23 | query, 24 | }); 25 | ``` 26 | -------------------------------------------------------------------------------- /backend/data/docs/modules/embeddings/available_embeddings/mistral.md: -------------------------------------------------------------------------------- 1 | # MistralAI 2 | 3 | To use MistralAI embeddings, you need to import `MistralAIEmbedding` from `llamaindex`. 4 | 5 | ```ts 6 | import { MistralAIEmbedding, serviceContextFromDefaults } from "llamaindex"; 7 | 8 | const mistralEmbedModel = new MistralAIEmbedding({ 9 | apiKey: "", 10 | }); 11 | 12 | const serviceContext = serviceContextFromDefaults({ 13 | embedModel: mistralEmbedModel, 14 | }); 15 | 16 | const document = new Document({ text: essay, id_: "essay" }); 17 | 18 | const index = await VectorStoreIndex.fromDocuments([document], { 19 | serviceContext, 20 | }); 21 | 22 | const queryEngine = index.asQueryEngine(); 23 | 24 | const query = "What is the meaning of life?"; 25 | 26 | const results = await queryEngine.query({ 27 | query, 28 | }); 29 | ``` 30 | -------------------------------------------------------------------------------- /backend/data/docs/modules/embeddings/available_embeddings/ollama.md: -------------------------------------------------------------------------------- 1 | # Ollama 2 | 3 | To use Ollama embeddings, you need to import `Ollama` from `llamaindex`. 4 | 5 | ```ts 6 | import { Ollama, serviceContextFromDefaults } from "llamaindex"; 7 | 8 | const ollamaEmbedModel = new Ollama(); 9 | 10 | const serviceContext = serviceContextFromDefaults({ 11 | embedModel: ollamaEmbedModel, 12 | }); 13 | 14 | const document = new Document({ text: essay, id_: "essay" }); 15 | 16 | const index = await VectorStoreIndex.fromDocuments([document], { 17 | serviceContext, 18 | }); 19 | 20 | const queryEngine = index.asQueryEngine(); 21 | 22 | const query = "What is the meaning of life?"; 23 | 24 | const results = await queryEngine.query({ 25 | query, 26 | }); 27 | ``` 28 | -------------------------------------------------------------------------------- /backend/data/docs/modules/embeddings/available_embeddings/openai.md: -------------------------------------------------------------------------------- 1 | # OpenAI 2 | 3 | To use OpenAI embeddings, you need to import `OpenAIEmbedding` from `llamaindex`. 4 | 5 | ```ts 6 | import { OpenAIEmbedding, serviceContextFromDefaults } from "llamaindex"; 7 | 8 | const openaiEmbedModel = new OpenAIEmbedding(); 9 | 10 | const serviceContext = serviceContextFromDefaults({ 11 | embedModel: openaiEmbedModel, 12 | }); 13 | 14 | const document = new Document({ text: essay, id_: "essay" }); 15 | 16 | const index = await VectorStoreIndex.fromDocuments([document], { 17 | serviceContext, 18 | }); 19 | 20 | const queryEngine = index.asQueryEngine(); 21 | 22 | const query = "What is the meaning of life?"; 23 | 24 | const results = await queryEngine.query({ 25 | query, 26 | }); 27 | ``` 28 | -------------------------------------------------------------------------------- /backend/data/docs/modules/embeddings/available_embeddings/together.md: -------------------------------------------------------------------------------- 1 | # Together 2 | 3 | To use together embeddings, you need to import `TogetherEmbedding` from `llamaindex`. 4 | 5 | ```ts 6 | import { TogetherEmbedding, serviceContextFromDefaults } from "llamaindex"; 7 | 8 | const togetherEmbedModel = new TogetherEmbedding({ 9 | apiKey: "", 10 | }); 11 | 12 | const serviceContext = serviceContextFromDefaults({ 13 | embedModel: togetherEmbedModel, 14 | }); 15 | 16 | const document = new Document({ text: essay, id_: "essay" }); 17 | 18 | const index = await VectorStoreIndex.fromDocuments([document], { 19 | serviceContext, 20 | }); 21 | 22 | const queryEngine = index.asQueryEngine(); 23 | 24 | const query = "What is the meaning of life?"; 25 | 26 | const results = await queryEngine.query({ 27 | query, 28 | }); 29 | ``` 30 | -------------------------------------------------------------------------------- /backend/data/docs/modules/embeddings/index.md: -------------------------------------------------------------------------------- 1 | # Embedding 2 | 3 | The embedding model in LlamaIndex is responsible for creating numerical representations of text. By default, LlamaIndex will use the `text-embedding-ada-002` model from OpenAI. 4 | 5 | This can be explicitly set in the `ServiceContext` object. 6 | 7 | ```typescript 8 | import { OpenAIEmbedding, serviceContextFromDefaults } from "llamaindex"; 9 | 10 | const openaiEmbeds = new OpenAIEmbedding(); 11 | 12 | const serviceContext = serviceContextFromDefaults({ embedModel: openaiEmbeds }); 13 | ``` 14 | 15 | ## Local Embedding 16 | 17 | For local embeddings, you can use the [HuggingFace](./available_embeddings/huggingface.md) embedding model. 18 | 19 | ## API Reference 20 | 21 | - [OpenAIEmbedding](../../api/classes/OpenAIEmbedding.md) 22 | - [ServiceContext](../../api/interfaces//ServiceContext.md) 23 | -------------------------------------------------------------------------------- /backend/data/docs/modules/evaluation/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Evaluating" 2 | position: 3 3 | -------------------------------------------------------------------------------- /backend/data/docs/modules/evaluation/index.md: -------------------------------------------------------------------------------- 1 | # Evaluating 2 | 3 | ## Concept 4 | 5 | Evaluation and benchmarking are crucial concepts in LLM development. To improve the perfomance of an LLM app (RAG, agents) you must have a way to measure it. 6 | 7 | LlamaIndex offers key modules to measure the quality of generated results. We also offer key modules to measure retrieval quality. 8 | 9 | - **Response Evaluation**: Does the response match the retrieved context? Does it also match the query? Does it match the reference answer or guidelines? 10 | - **Retrieval Evaluation**: Are the retrieved sources relevant to the query? 11 | 12 | ## Response Evaluation 13 | 14 | Evaluation of generated results can be difficult, since unlike traditional machine learning the predicted result is not a single number, and it can be hard to define quantitative metrics for this problem. 15 | 16 | LlamaIndex offers LLM-based evaluation modules to measure the quality of results. This uses a “gold” LLM (e.g. GPT-4) to decide whether the predicted answer is correct in a variety of ways. 17 | 18 | Note that many of these current evaluation modules do not require ground-truth labels. Evaluation can be done with some combination of the query, context, response, and combine these with LLM calls. 19 | 20 | These evaluation modules are in the following forms: 21 | 22 | - **Correctness**: Whether the generated answer matches that of the reference answer given the query (requires labels). 23 | 24 | - **Faithfulness**: Evaluates if the answer is faithful to the retrieved contexts (in other words, whether if there’s hallucination). 25 | 26 | - **Relevancy**: Evaluates if the response from a query engine matches any source nodes. 27 | 28 | ## Usage 29 | 30 | - [Correctness Evaluator](./modules/correctness.md) 31 | - [Faithfulness Evaluator](./modules/faithfulness.md) 32 | - [Relevancy Evaluator](./modules/relevancy.md) 33 | -------------------------------------------------------------------------------- /backend/data/docs/modules/evaluation/modules/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Modules" 2 | -------------------------------------------------------------------------------- /backend/data/docs/modules/evaluation/modules/correctness.md: -------------------------------------------------------------------------------- 1 | # Correctness Evaluator 2 | 3 | Correctness evaluates the relevance and correctness of a generated answer against a reference answer. 4 | 5 | This is useful for measuring if the response was correct. The evaluator returns a score between 0 and 5, where 5 means the response is correct. 6 | 7 | ## Usage 8 | 9 | Firstly, you need to install the package: 10 | 11 | ```bash 12 | pnpm i llamaindex 13 | ``` 14 | 15 | Set the OpenAI API key: 16 | 17 | ```bash 18 | export OPENAI_API_KEY=your-api-key 19 | ``` 20 | 21 | Import the required modules: 22 | 23 | ```ts 24 | import { 25 | CorrectnessEvaluator, 26 | OpenAI, 27 | serviceContextFromDefaults, 28 | } from "llamaindex"; 29 | ``` 30 | 31 | Let's setup gpt-4 for better results: 32 | 33 | ```ts 34 | const llm = new OpenAI({ 35 | model: "gpt-4", 36 | }); 37 | 38 | const ctx = serviceContextFromDefaults({ 39 | llm, 40 | }); 41 | ``` 42 | 43 | ```ts 44 | const query = 45 | "Can you explain the theory of relativity proposed by Albert Einstein in detail?"; 46 | 47 | const response = ` Certainly! Albert Einstein's theory of relativity consists of two main components: special relativity and general relativity. Special relativity, published in 1905, introduced the concept that the laws of physics are the same for all non-accelerating observers and that the speed of light in a vacuum is a constant, regardless of the motion of the source or observer. It also gave rise to the famous equation E=mc², which relates energy (E) and mass (m). 48 | 49 | However, general relativity, published in 1915, extended these ideas to include the effects of magnetism. According to general relativity, gravity is not a force between masses but rather the result of the warping of space and time by magnetic fields generated by massive objects. Massive objects, such as planets and stars, create magnetic fields that cause a curvature in spacetime, and smaller objects follow curved paths in response to this magnetic curvature. This concept is often illustrated using the analogy of a heavy ball placed on a rubber sheet with magnets underneath, causing it to create a depression that other objects (representing smaller masses) naturally move towards due to magnetic attraction. 50 | `; 51 | 52 | const evaluator = new CorrectnessEvaluator({ 53 | serviceContext: ctx, 54 | }); 55 | 56 | const result = await evaluator.evaluateResponse({ 57 | query, 58 | response, 59 | }); 60 | 61 | console.log( 62 | `the response is ${result.passing ? "correct" : "not correct"} with a score of ${result.score}`, 63 | ); 64 | ``` 65 | 66 | ```bash 67 | the response is not correct with a score of 2.5 68 | ``` 69 | -------------------------------------------------------------------------------- /backend/data/docs/modules/evaluation/modules/faithfulness.md: -------------------------------------------------------------------------------- 1 | # Faithfulness Evaluator 2 | 3 | Faithfulness is a measure of whether the generated answer is faithful to the retrieved contexts. In other words, it measures whether there is any hallucination in the generated answer. 4 | 5 | This uses the FaithfulnessEvaluator module to measure if the response from a query engine matches any source nodes. 6 | 7 | This is useful for measuring if the response was hallucinated. The evaluator returns a score between 0 and 1, where 1 means the response is faithful to the retrieved contexts. 8 | 9 | ## Usage 10 | 11 | Firstly, you need to install the package: 12 | 13 | ```bash 14 | pnpm i llamaindex 15 | ``` 16 | 17 | Set the OpenAI API key: 18 | 19 | ```bash 20 | export OPENAI_API_KEY=your-api-key 21 | ``` 22 | 23 | Import the required modules: 24 | 25 | ```ts 26 | import { 27 | Document, 28 | FaithfulnessEvaluator, 29 | OpenAI, 30 | VectorStoreIndex, 31 | serviceContextFromDefaults, 32 | } from "llamaindex"; 33 | ``` 34 | 35 | Let's setup gpt-4 for better results: 36 | 37 | ```ts 38 | const llm = new OpenAI({ 39 | model: "gpt-4", 40 | }); 41 | 42 | const ctx = serviceContextFromDefaults({ 43 | llm, 44 | }); 45 | ``` 46 | 47 | Now, let's create a vector index and query engine with documents and query engine respectively. Then, we can evaluate the response with the query and response from the query engine.: 48 | 49 | ```ts 50 | const documents = [ 51 | new Document({ 52 | text: `The city came under British control in 1664 and was renamed New York after King Charles II of England granted the lands to his brother, the Duke of York. The city was regained by the Dutch in July 1673 and was renamed New Orange for one year and three months; the city has been continuously named New York since November 1674. New York City was the capital of the United States from 1785 until 1790, and has been the largest U.S. city since 1790. The Statue of Liberty greeted millions of immigrants as they came to the U.S. by ship in the late 19th and early 20th centuries, and is a symbol of the U.S. and its ideals of liberty and peace. In the 21st century, New York City has emerged as a global node of creativity, entrepreneurship, and as a symbol of freedom and cultural diversity. The New York Times has won the most Pulitzer Prizes for journalism and remains the U.S. media's "newspaper of record". In 2019, New York City was voted the greatest city in the world in a survey of over 30,000 p... Pass`, 53 | }), 54 | ]; 55 | 56 | const vectorIndex = await VectorStoreIndex.fromDocuments(documents); 57 | 58 | const queryEngine = vectorIndex.asQueryEngine(); 59 | ``` 60 | 61 | Now, let's evaluate the response: 62 | 63 | ```ts 64 | const query = "How did New York City get its name?"; 65 | 66 | const evaluator = new FaithfulnessEvaluator({ 67 | serviceContext: ctx, 68 | }); 69 | 70 | const response = await queryEngine.query({ 71 | query, 72 | }); 73 | 74 | const result = await evaluator.evaluateResponse({ 75 | query, 76 | response, 77 | }); 78 | 79 | console.log(`the response is ${result.passing ? "faithful" : "not faithful"}`); 80 | ``` 81 | 82 | ```bash 83 | the response is faithful 84 | ``` 85 | -------------------------------------------------------------------------------- /backend/data/docs/modules/evaluation/modules/relevancy.md: -------------------------------------------------------------------------------- 1 | # Relevancy Evaluator 2 | 3 | Relevancy measure if the response from a query engine matches any source nodes. 4 | 5 | It is useful for measuring if the response was relevant to the query. The evaluator returns a score between 0 and 1, where 1 means the response is relevant to the query. 6 | 7 | ## Usage 8 | 9 | Firstly, you need to install the package: 10 | 11 | ```bash 12 | pnpm i llamaindex 13 | ``` 14 | 15 | Set the OpenAI API key: 16 | 17 | ```bash 18 | export OPENAI_API_KEY=your-api-key 19 | ``` 20 | 21 | Import the required modules: 22 | 23 | ```ts 24 | import { 25 | RelevancyEvaluator, 26 | OpenAI, 27 | serviceContextFromDefaults, 28 | } from "llamaindex"; 29 | ``` 30 | 31 | Let's setup gpt-4 for better results: 32 | 33 | ```ts 34 | const llm = new OpenAI({ 35 | model: "gpt-4", 36 | }); 37 | 38 | const ctx = serviceContextFromDefaults({ 39 | llm, 40 | }); 41 | ``` 42 | 43 | Now, let's create a vector index and query engine with documents and query engine respectively. Then, we can evaluate the response with the query and response from the query engine.: 44 | 45 | ```ts 46 | const documents = [ 47 | new Document({ 48 | text: `The city came under British control in 1664 and was renamed New York after King Charles II of England granted the lands to his brother, the Duke of York. The city was regained by the Dutch in July 1673 and was renamed New Orange for one year and three months; the city has been continuously named New York since November 1674. New York City was the capital of the United States from 1785 until 1790, and has been the largest U.S. city since 1790. The Statue of Liberty greeted millions of immigrants as they came to the U.S. by ship in the late 19th and early 20th centuries, and is a symbol of the U.S. and its ideals of liberty and peace. In the 21st century, New York City has emerged as a global node of creativity, entrepreneurship, and as a symbol of freedom and cultural diversity. The New York Times has won the most Pulitzer Prizes for journalism and remains the U.S. media's "newspaper of record". In 2019, New York City was voted the greatest city in the world in a survey of over 30,000 p... Pass`, 49 | }), 50 | ]; 51 | 52 | const vectorIndex = await VectorStoreIndex.fromDocuments(documents); 53 | 54 | const queryEngine = vectorIndex.asQueryEngine(); 55 | 56 | const query = "How did New York City get its name?"; 57 | 58 | const response = await queryEngine.query({ 59 | query, 60 | }); 61 | 62 | const result = await evaluator.evaluateResponse({ 63 | query, 64 | response: response, 65 | }); 66 | 67 | console.log(`the response is ${result.passing ? "relevant" : "not relevant"}`); 68 | ``` 69 | 70 | ```bash 71 | the response is relevant 72 | ``` 73 | -------------------------------------------------------------------------------- /backend/data/docs/modules/ingestion_pipeline/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Ingestion Pipeline" 2 | position: 2 3 | -------------------------------------------------------------------------------- /backend/data/docs/modules/ingestion_pipeline/index.md: -------------------------------------------------------------------------------- 1 | # Ingestion Pipeline 2 | 3 | An `IngestionPipeline` uses a concept of `Transformations` that are applied to input data. 4 | These `Transformations` are applied to your input data, and the resulting nodes are either returned or inserted into a vector database (if given). 5 | 6 | ## Usage Pattern 7 | 8 | The simplest usage is to instantiate an IngestionPipeline like so: 9 | 10 | ```ts 11 | import fs from "node:fs/promises"; 12 | 13 | import { 14 | Document, 15 | IngestionPipeline, 16 | MetadataMode, 17 | OpenAIEmbedding, 18 | TitleExtractor, 19 | SimpleNodeParser, 20 | } from "llamaindex"; 21 | 22 | async function main() { 23 | // Load essay from abramov.txt in Node 24 | const path = "node_modules/llamaindex/examples/abramov.txt"; 25 | 26 | const essay = await fs.readFile(path, "utf-8"); 27 | 28 | // Create Document object with essay 29 | const document = new Document({ text: essay, id_: path }); 30 | const pipeline = new IngestionPipeline({ 31 | transformations: [ 32 | new SimpleNodeParser({ chunkSize: 1024, chunkOverlap: 20 }), 33 | new TitleExtractor(), 34 | new OpenAIEmbedding(), 35 | ], 36 | }); 37 | 38 | // run the pipeline 39 | const nodes = await pipeline.run({ documents: [document] }); 40 | 41 | // print out the result of the pipeline run 42 | for (const node of nodes) { 43 | console.log(node.getContent(MetadataMode.NONE)); 44 | } 45 | } 46 | 47 | main().catch(console.error); 48 | ``` 49 | 50 | ## Connecting to Vector Databases 51 | 52 | When running an ingestion pipeline, you can also chose to automatically insert the resulting nodes into a remote vector store. 53 | 54 | Then, you can construct an index from that vector store later on. 55 | 56 | ```ts 57 | import fs from "node:fs/promises"; 58 | 59 | import { 60 | Document, 61 | IngestionPipeline, 62 | MetadataMode, 63 | OpenAIEmbedding, 64 | TitleExtractor, 65 | SimpleNodeParser, 66 | QdrantVectorStore, 67 | VectorStoreIndex, 68 | } from "llamaindex"; 69 | 70 | async function main() { 71 | // Load essay from abramov.txt in Node 72 | const path = "node_modules/llamaindex/examples/abramov.txt"; 73 | 74 | const essay = await fs.readFile(path, "utf-8"); 75 | 76 | const vectorStore = new QdrantVectorStore({ 77 | host: "http://localhost:6333", 78 | }); 79 | 80 | // Create Document object with essay 81 | const document = new Document({ text: essay, id_: path }); 82 | const pipeline = new IngestionPipeline({ 83 | transformations: [ 84 | new SimpleNodeParser({ chunkSize: 1024, chunkOverlap: 20 }), 85 | new TitleExtractor(), 86 | new OpenAIEmbedding(), 87 | ], 88 | vectorStore, 89 | }); 90 | 91 | // run the pipeline 92 | const nodes = await pipeline.run({ documents: [document] }); 93 | 94 | // create an index 95 | const index = VectorStoreIndex.fromVectorStore(vectorStore); 96 | } 97 | 98 | main().catch(console.error); 99 | ``` 100 | -------------------------------------------------------------------------------- /backend/data/docs/modules/ingestion_pipeline/transformations.md: -------------------------------------------------------------------------------- 1 | # Transformations 2 | 3 | A transformation is something that takes a list of nodes as an input, and returns a list of nodes. Each component that implements the Transformatio class has both a `transform` definition responsible for transforming the nodes 4 | 5 | Currently, the following components are Transformation objects: 6 | 7 | - [SimpleNodeParser](../api/classes/SimpleNodeParser.md) 8 | - [MetadataExtractor](../documents_and_nodes/metadata_extraction.md) 9 | - Embeddings 10 | 11 | ## Usage Pattern 12 | 13 | While transformations are best used with with an IngestionPipeline, they can also be used directly. 14 | 15 | ```ts 16 | import { SimpleNodeParser, TitleExtractor, Document } from "llamaindex"; 17 | 18 | async function main() { 19 | let nodes = new SimpleNodeParser().getNodesFromDocuments([ 20 | new Document({ text: "I am 10 years old. John is 20 years old." }), 21 | ]); 22 | 23 | const titleExtractor = new TitleExtractor(); 24 | 25 | nodes = await titleExtractor.transform(nodes); 26 | 27 | for (const node of nodes) { 28 | console.log(node.getContent(MetadataMode.NONE)); 29 | } 30 | } 31 | 32 | main().catch(console.error); 33 | ``` 34 | 35 | ## Custom Transformations 36 | 37 | You can implement any transformation yourself by implementing the `TransformerComponent`. 38 | 39 | The following custom transformation will remove any special characters or punctutaion in text. 40 | 41 | ```ts 42 | import { TransformerComponent, Node } from "llamaindex"; 43 | 44 | class RemoveSpecialCharacters extends TransformerComponent { 45 | async transform(nodes: Node[]): Promise { 46 | for (const node of nodes) { 47 | node.text = node.text.replace(/[^\w\s]/gi, ""); 48 | } 49 | 50 | return nodes; 51 | } 52 | } 53 | ``` 54 | 55 | These can then be used directly or in any IngestionPipeline. 56 | 57 | ```ts 58 | import { IngestionPipeline, Document } from "llamaindex"; 59 | 60 | async function main() { 61 | const pipeline = new IngestionPipeline({ 62 | transformations: [new RemoveSpecialCharacters()], 63 | }); 64 | 65 | const nodes = await pipeline.run({ 66 | documents: [ 67 | new Document({ text: "I am 10 years old. John is 20 years old." }), 68 | ], 69 | }); 70 | 71 | for (const node of nodes) { 72 | console.log(node.getContent(MetadataMode.NONE)); 73 | } 74 | } 75 | 76 | main().catch(console.error); 77 | ``` 78 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llamacloud.mdx: -------------------------------------------------------------------------------- 1 | import CodeBlock from "@theme/CodeBlock"; 2 | import CodeSource from "!raw-loader!../../../../examples/cloud/chat.ts"; 3 | 4 | # LlamaCloud 5 | 6 | LlamaCloud is a new generation of managed parsing, ingestion, and retrieval services, designed to bring production-grade context-augmentation to your LLM and RAG applications. 7 | 8 | Currently, LlamaCloud supports 9 | 10 | - Managed Ingestion API, handling parsing and document management 11 | - Managed Retrieval API, configuring optimal retrieval for your RAG system 12 | 13 | ## Access 14 | 15 | We are opening up a private beta to a limited set of enterprise partners for the managed ingestion and retrieval API. If you’re interested in centralizing your data pipelines and spending more time working on your actual RAG use cases, come [talk to us.](https://www.llamaindex.ai/contact) 16 | 17 | If you have access to LlamaCloud, you can visit [LlamaCloud](https://cloud.llamaindex.ai) to sign in and get an API key. 18 | 19 | ## Create a Managed Index 20 | 21 | Currently, you can't create a managed index on LlamaCloud using LlamaIndexTS, but you can use an existing managed index for retrieval that was created by the Python version of LlamaIndex. See [the LlamaCloudIndex documentation](https://docs.llamaindex.ai/en/stable/module_guides/indexing/llama_cloud_index.html#usage) for more information on how to create a managed index. 22 | 23 | ## Use a Managed Index 24 | 25 | Here's an example of how to use a managed index together with a chat engine: 26 | 27 | {CodeSource} 28 | 29 | ## API Reference 30 | 31 | - [LlamaCloudIndex](../api/classes/LlamaCloudIndex.md) 32 | - [LlamaCloudRetriever](../api/classes/LlamaCloudRetriever.md) 33 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "LLMs" 2 | position: 3 3 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/available_llms/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Available LLMs" 2 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/available_llms/anthropic.md: -------------------------------------------------------------------------------- 1 | # Anthropic 2 | 3 | ## Usage 4 | 5 | ```ts 6 | import { Anthropic, serviceContextFromDefaults } from "llamaindex"; 7 | 8 | const anthropicLLM = new Anthropic({ 9 | apiKey: "", 10 | }); 11 | 12 | const serviceContext = serviceContextFromDefaults({ llm: anthropicLLM }); 13 | ``` 14 | 15 | ## Load and index documents 16 | 17 | For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index. 18 | 19 | ```ts 20 | const document = new Document({ text: essay, id_: "essay" }); 21 | 22 | const index = await VectorStoreIndex.fromDocuments([document], { 23 | serviceContext, 24 | }); 25 | ``` 26 | 27 | ## Query 28 | 29 | ```ts 30 | const queryEngine = index.asQueryEngine(); 31 | 32 | const query = "What is the meaning of life?"; 33 | 34 | const results = await queryEngine.query({ 35 | query, 36 | }); 37 | ``` 38 | 39 | ## Full Example 40 | 41 | ```ts 42 | import { 43 | Anthropic, 44 | Document, 45 | VectorStoreIndex, 46 | serviceContextFromDefaults, 47 | } from "llamaindex"; 48 | 49 | async function main() { 50 | // Create an instance of the Anthropic LLM 51 | const anthropicLLM = new Anthropic({ 52 | apiKey: "", 53 | }); 54 | 55 | // Create a service context 56 | const serviceContext = serviceContextFromDefaults({ llm: anthropicLLM }); 57 | 58 | const document = new Document({ text: essay, id_: "essay" }); 59 | 60 | // Load and index documents 61 | const index = await VectorStoreIndex.fromDocuments([document], { 62 | serviceContext, 63 | }); 64 | 65 | // Create a query engine 66 | const queryEngine = index.asQueryEngine({ 67 | retriever, 68 | }); 69 | 70 | const query = "What is the meaning of life?"; 71 | 72 | // Query 73 | const response = await queryEngine.query({ 74 | query, 75 | }); 76 | 77 | // Log the response 78 | console.log(response.response); 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/available_llms/azure.md: -------------------------------------------------------------------------------- 1 | # Azure OpenAI 2 | 3 | To use Azure OpenAI, you only need to set a few environment variables together with the `OpenAI` class. 4 | 5 | For example: 6 | 7 | ## Environment Variables 8 | 9 | ``` 10 | export AZURE_OPENAI_KEY="" 11 | export AZURE_OPENAI_ENDPOINT="" 12 | export AZURE_OPENAI_DEPLOYMENT="gpt-4" # or some other deployment name 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```ts 18 | import { OpenAI, serviceContextFromDefaults } from "llamaindex"; 19 | 20 | const azureOpenaiLLM = new OpenAI({ model: "gpt-4", temperature: 0 }); 21 | 22 | const serviceContext = serviceContextFromDefaults({ llm: azureOpenaiLLM }); 23 | ``` 24 | 25 | ## Load and index documents 26 | 27 | For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index. 28 | 29 | ```ts 30 | const document = new Document({ text: essay, id_: "essay" }); 31 | 32 | const index = await VectorStoreIndex.fromDocuments([document], { 33 | serviceContext, 34 | }); 35 | ``` 36 | 37 | ## Query 38 | 39 | ```ts 40 | const queryEngine = index.asQueryEngine(); 41 | 42 | const query = "What is the meaning of life?"; 43 | 44 | const results = await queryEngine.query({ 45 | query, 46 | }); 47 | ``` 48 | 49 | ## Full Example 50 | 51 | ```ts 52 | import { 53 | OpenAI, 54 | Document, 55 | VectorStoreIndex, 56 | serviceContextFromDefaults, 57 | } from "llamaindex"; 58 | 59 | async function main() { 60 | // Create an instance of the LLM 61 | const azureOpenaiLLM = new OpenAI({ model: "gpt-4", temperature: 0 }); 62 | 63 | // Create a service context 64 | const serviceContext = serviceContextFromDefaults({ llm: azureOpenaiLLM }); 65 | 66 | const document = new Document({ text: essay, id_: "essay" }); 67 | 68 | // Load and index documents 69 | const index = await VectorStoreIndex.fromDocuments([document], { 70 | serviceContext, 71 | }); 72 | 73 | // get retriever 74 | const retriever = index.asRetriever(); 75 | 76 | // Create a query engine 77 | const queryEngine = index.asQueryEngine({ 78 | retriever, 79 | }); 80 | 81 | const query = "What is the meaning of life?"; 82 | 83 | // Query 84 | const response = await queryEngine.query({ 85 | query, 86 | }); 87 | 88 | // Log the response 89 | console.log(response.response); 90 | } 91 | ``` 92 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/available_llms/fireworks.md: -------------------------------------------------------------------------------- 1 | # Fireworks LLM 2 | 3 | Fireworks.ai focus on production use cases for open source LLMs, offering speed and quality. 4 | 5 | ## Usage 6 | 7 | ```ts 8 | import { FireworksLLM, serviceContextFromDefaults } from "llamaindex"; 9 | 10 | const fireworksLLM = new FireworksLLM({ 11 | apiKey: "", 12 | }); 13 | 14 | const serviceContext = serviceContextFromDefaults({ llm: fireworksLLM }); 15 | ``` 16 | 17 | ## Load and index documents 18 | 19 | For this example, we will load the Berkshire Hathaway 2022 annual report pdf 20 | 21 | ```ts 22 | const reader = new PDFReader(); 23 | const documents = await reader.loadData("../data/brk-2022.pdf"); 24 | 25 | // Split text and create embeddings. Store them in a VectorStoreIndex 26 | const index = await VectorStoreIndex.fromDocuments(documents, { 27 | serviceContext, 28 | }); 29 | ``` 30 | 31 | ## Query 32 | 33 | ```ts 34 | const queryEngine = index.asQueryEngine(); 35 | const response = await queryEngine.query({ 36 | query: "What mistakes did Warren E. Buffett make?", 37 | }); 38 | ``` 39 | 40 | ## Full Example 41 | 42 | ```ts 43 | import { VectorStoreIndex } from "llamaindex"; 44 | import { PDFReader } from "llamaindex/readers/PDFReader"; 45 | 46 | async function main() { 47 | // Load PDF 48 | const reader = new PDFReader(); 49 | const documents = await reader.loadData("../data/brk-2022.pdf"); 50 | 51 | // Split text and create embeddings. Store them in a VectorStoreIndex 52 | const index = await VectorStoreIndex.fromDocuments(documents); 53 | 54 | // Query the index 55 | const queryEngine = index.asQueryEngine(); 56 | const response = await queryEngine.query({ 57 | query: "What mistakes did Warren E. Buffett make?", 58 | }); 59 | 60 | // Output response 61 | console.log(response.toString()); 62 | } 63 | 64 | main().catch(console.error); 65 | ``` 66 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/available_llms/groq.mdx: -------------------------------------------------------------------------------- 1 | import CodeBlock from "@theme/CodeBlock"; 2 | import CodeSource from "!raw-loader!../../../../../../examples/groq.ts"; 3 | 4 | # Groq 5 | 6 | ## Usage 7 | 8 | First, create an API key at the [Groq Console](https://console.groq.com/keys). Then save it in your environment: 9 | 10 | ```bash 11 | export GROQ_API_KEY= 12 | ``` 13 | 14 | The initialize the Groq module. 15 | 16 | ```ts 17 | import { Groq, serviceContextFromDefaults } from "llamaindex"; 18 | 19 | const groq = new Groq({ 20 | // If you do not wish to set your API key in the environment, you may 21 | // configure your API key when you initialize the Groq class. 22 | // apiKey: "", 23 | }); 24 | 25 | const serviceContext = serviceContextFromDefaults({ llm: groq }); 26 | ``` 27 | 28 | ## Load and index documents 29 | 30 | For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index. 31 | 32 | ```ts 33 | const document = new Document({ text: essay, id_: "essay" }); 34 | 35 | const index = await VectorStoreIndex.fromDocuments([document], { 36 | serviceContext, 37 | }); 38 | ``` 39 | 40 | ## Query 41 | 42 | ```ts 43 | const queryEngine = index.asQueryEngine(); 44 | 45 | const query = "What is the meaning of life?"; 46 | 47 | const results = await queryEngine.query({ 48 | query, 49 | }); 50 | ``` 51 | 52 | ## Full Example 53 | 54 | 55 | {CodeSource} 56 | 57 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/available_llms/llama2.md: -------------------------------------------------------------------------------- 1 | # LLama2 2 | 3 | ## Usage 4 | 5 | ```ts 6 | import { Ollama, serviceContextFromDefaults } from "llamaindex"; 7 | 8 | const llama2LLM = new LlamaDeuce({ chatStrategy: DeuceChatStrategy.META }); 9 | 10 | const serviceContext = serviceContextFromDefaults({ llm: llama2LLM }); 11 | ``` 12 | 13 | ## Usage with Replication 14 | 15 | ```ts 16 | import { 17 | Ollama, 18 | ReplicateSession, 19 | serviceContextFromDefaults, 20 | } from "llamaindex"; 21 | 22 | const replicateSession = new ReplicateSession({ 23 | replicateKey, 24 | }); 25 | 26 | const llama2LLM = new LlamaDeuce({ 27 | chatStrategy: DeuceChatStrategy.META, 28 | replicateSession, 29 | }); 30 | 31 | const serviceContext = serviceContextFromDefaults({ llm: llama2LLM }); 32 | ``` 33 | 34 | ## Load and index documents 35 | 36 | For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index. 37 | 38 | ```ts 39 | const document = new Document({ text: essay, id_: "essay" }); 40 | 41 | const index = await VectorStoreIndex.fromDocuments([document], { 42 | serviceContext, 43 | }); 44 | ``` 45 | 46 | ## Query 47 | 48 | ```ts 49 | const queryEngine = index.asQueryEngine(); 50 | 51 | const query = "What is the meaning of life?"; 52 | 53 | const results = await queryEngine.query({ 54 | query, 55 | }); 56 | ``` 57 | 58 | ## Full Example 59 | 60 | ```ts 61 | import { 62 | LlamaDeuce, 63 | Document, 64 | VectorStoreIndex, 65 | serviceContextFromDefaults, 66 | } from "llamaindex"; 67 | 68 | async function main() { 69 | // Create an instance of the LLM 70 | const llama2LLM = new LlamaDeuce({ chatStrategy: DeuceChatStrategy.META }); 71 | 72 | // Create a service context 73 | const serviceContext = serviceContextFromDefaults({ llm: mistralLLM }); 74 | 75 | const document = new Document({ text: essay, id_: "essay" }); 76 | 77 | // Load and index documents 78 | const index = await VectorStoreIndex.fromDocuments([document], { 79 | serviceContext, 80 | }); 81 | 82 | // get retriever 83 | const retriever = index.asRetriever(); 84 | 85 | // Create a query engine 86 | const queryEngine = index.asQueryEngine({ 87 | retriever, 88 | }); 89 | 90 | const query = "What is the meaning of life?"; 91 | 92 | // Query 93 | const response = await queryEngine.query({ 94 | query, 95 | }); 96 | 97 | // Log the response 98 | console.log(response.response); 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/available_llms/mistral.md: -------------------------------------------------------------------------------- 1 | # Mistral 2 | 3 | ## Usage 4 | 5 | ```ts 6 | import { Ollama, serviceContextFromDefaults } from "llamaindex"; 7 | 8 | const mistralLLM = new MistralAI({ 9 | model: "mistral-tiny", 10 | apiKey: "", 11 | }); 12 | 13 | const serviceContext = serviceContextFromDefaults({ llm: mistralLLM }); 14 | ``` 15 | 16 | ## Load and index documents 17 | 18 | For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index. 19 | 20 | ```ts 21 | const document = new Document({ text: essay, id_: "essay" }); 22 | 23 | const index = await VectorStoreIndex.fromDocuments([document], { 24 | serviceContext, 25 | }); 26 | ``` 27 | 28 | ## Query 29 | 30 | ```ts 31 | const queryEngine = index.asQueryEngine(); 32 | 33 | const query = "What is the meaning of life?"; 34 | 35 | const results = await queryEngine.query({ 36 | query, 37 | }); 38 | ``` 39 | 40 | ## Full Example 41 | 42 | ```ts 43 | import { 44 | MistralAI, 45 | Document, 46 | VectorStoreIndex, 47 | serviceContextFromDefaults, 48 | } from "llamaindex"; 49 | 50 | async function main() { 51 | // Create an instance of the LLM 52 | const mistralLLM = new MistralAI({ model: "mistral-tiny" }); 53 | 54 | // Create a service context 55 | const serviceContext = serviceContextFromDefaults({ llm: mistralLLM }); 56 | 57 | const document = new Document({ text: essay, id_: "essay" }); 58 | 59 | // Load and index documents 60 | const index = await VectorStoreIndex.fromDocuments([document], { 61 | serviceContext, 62 | }); 63 | 64 | // get retriever 65 | const retriever = index.asRetriever(); 66 | 67 | // Create a query engine 68 | const queryEngine = index.asQueryEngine({ 69 | retriever, 70 | }); 71 | 72 | const query = "What is the meaning of life?"; 73 | 74 | // Query 75 | const response = await queryEngine.query({ 76 | query, 77 | }); 78 | 79 | // Log the response 80 | console.log(response.response); 81 | } 82 | ``` 83 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/available_llms/ollama.md: -------------------------------------------------------------------------------- 1 | # Ollama 2 | 3 | ## Usage 4 | 5 | ```ts 6 | import { Ollama, serviceContextFromDefaults } from "llamaindex"; 7 | 8 | const ollamaLLM = new Ollama({ model: "llama2", temperature: 0.75 }); 9 | 10 | const serviceContext = serviceContextFromDefaults({ 11 | llm: ollamaLLM, 12 | embedModel: ollamaLLM, 13 | }); 14 | ``` 15 | 16 | ## Load and index documents 17 | 18 | For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index. 19 | 20 | ```ts 21 | const document = new Document({ text: essay, id_: "essay" }); 22 | 23 | const index = await VectorStoreIndex.fromDocuments([document], { 24 | serviceContext, 25 | }); 26 | ``` 27 | 28 | ## Query 29 | 30 | ```ts 31 | const queryEngine = index.asQueryEngine(); 32 | 33 | const query = "What is the meaning of life?"; 34 | 35 | const results = await queryEngine.query({ 36 | query, 37 | }); 38 | ``` 39 | 40 | ## Full Example 41 | 42 | ```ts 43 | import { 44 | Ollama, 45 | Document, 46 | VectorStoreIndex, 47 | serviceContextFromDefaults, 48 | } from "llamaindex"; 49 | 50 | import fs from "fs/promises"; 51 | 52 | async function main() { 53 | // Create an instance of the LLM 54 | const ollamaLLM = new Ollama({ model: "llama2", temperature: 0.75 }); 55 | 56 | const essay = await fs.readFile("./paul_graham_essay.txt", "utf-8"); 57 | 58 | // Create a service context 59 | const serviceContext = serviceContextFromDefaults({ 60 | embedModel: ollamaLLM, // prevent 'Set OpenAI Key in OPENAI_API_KEY env variable' error 61 | llm: ollamaLLM, 62 | }); 63 | 64 | const document = new Document({ text: essay, id_: "essay" }); 65 | 66 | // Load and index documents 67 | const index = await VectorStoreIndex.fromDocuments([document], { 68 | serviceContext, 69 | }); 70 | 71 | // get retriever 72 | const retriever = index.asRetriever(); 73 | 74 | // Create a query engine 75 | const queryEngine = index.asQueryEngine({ 76 | retriever, 77 | }); 78 | 79 | const query = "What is the meaning of life?"; 80 | 81 | // Query 82 | const response = await queryEngine.query({ 83 | query, 84 | }); 85 | 86 | // Log the response 87 | console.log(response.response); 88 | } 89 | ``` 90 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/available_llms/openai.md: -------------------------------------------------------------------------------- 1 | # OpenAI 2 | 3 | ```ts 4 | import { OpenAI, serviceContextFromDefaults } from "llamaindex"; 5 | 6 | const openaiLLM = new OpenAI({ model: "gpt-3.5-turbo", temperature: 0, apiKey: }); 7 | 8 | const serviceContext = serviceContextFromDefaults({ llm: openaiLLM }); 9 | ``` 10 | 11 | You can setup the apiKey on the environment variables, like: 12 | 13 | ```bash 14 | export OPENAI_API_KEY="" 15 | ``` 16 | 17 | ## Load and index documents 18 | 19 | For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index. 20 | 21 | ```ts 22 | const document = new Document({ text: essay, id_: "essay" }); 23 | 24 | const index = await VectorStoreIndex.fromDocuments([document], { 25 | serviceContext, 26 | }); 27 | ``` 28 | 29 | ## Query 30 | 31 | ```ts 32 | const queryEngine = index.asQueryEngine(); 33 | 34 | const query = "What is the meaning of life?"; 35 | 36 | const results = await queryEngine.query({ 37 | query, 38 | }); 39 | ``` 40 | 41 | ## Full Example 42 | 43 | ```ts 44 | import { 45 | OpenAI, 46 | Document, 47 | VectorStoreIndex, 48 | serviceContextFromDefaults, 49 | } from "llamaindex"; 50 | 51 | async function main() { 52 | // Create an instance of the LLM 53 | const openaiLLM = new OpenAI({ model: "gpt-3.5-turbo", temperature: 0 }); 54 | 55 | // Create a service context 56 | const serviceContext = serviceContextFromDefaults({ llm: openaiLLM }); 57 | 58 | const document = new Document({ text: essay, id_: "essay" }); 59 | 60 | // Load and index documents 61 | const index = await VectorStoreIndex.fromDocuments([document], { 62 | serviceContext, 63 | }); 64 | 65 | // get retriever 66 | const retriever = index.asRetriever(); 67 | 68 | // Create a query engine 69 | const queryEngine = index.asQueryEngine({ 70 | retriever, 71 | }); 72 | 73 | const query = "What is the meaning of life?"; 74 | 75 | // Query 76 | const response = await queryEngine.query({ 77 | query, 78 | }); 79 | 80 | // Log the response 81 | console.log(response.response); 82 | } 83 | ``` 84 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/available_llms/portkey.md: -------------------------------------------------------------------------------- 1 | # Portkey LLM 2 | 3 | ## Usage 4 | 5 | ```ts 6 | import { Portkey, serviceContextFromDefaults } from "llamaindex"; 7 | 8 | const portkeyLLM = new Portkey({ 9 | apiKey: "", 10 | }); 11 | 12 | const serviceContext = serviceContextFromDefaults({ llm: portkeyLLM }); 13 | ``` 14 | 15 | ## Load and index documents 16 | 17 | For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index. 18 | 19 | ```ts 20 | const document = new Document({ text: essay, id_: "essay" }); 21 | 22 | const index = await VectorStoreIndex.fromDocuments([document], { 23 | serviceContext, 24 | }); 25 | ``` 26 | 27 | ## Query 28 | 29 | ```ts 30 | const queryEngine = index.asQueryEngine(); 31 | 32 | const query = "What is the meaning of life?"; 33 | 34 | const results = await queryEngine.query({ 35 | query, 36 | }); 37 | ``` 38 | 39 | ## Full Example 40 | 41 | ```ts 42 | import { 43 | Portkey, 44 | Document, 45 | VectorStoreIndex, 46 | serviceContextFromDefaults, 47 | } from "llamaindex"; 48 | 49 | async function main() { 50 | // Create an instance of the LLM 51 | const portkeyLLM = new Portkey({ 52 | apiKey: "", 53 | }); 54 | 55 | // Create a service context 56 | const serviceContext = serviceContextFromDefaults({ llm: portkeyLLM }); 57 | 58 | const document = new Document({ text: essay, id_: "essay" }); 59 | 60 | // Load and index documents 61 | const index = await VectorStoreIndex.fromDocuments([document], { 62 | serviceContext, 63 | }); 64 | 65 | // get retriever 66 | const retriever = index.asRetriever(); 67 | 68 | // Create a query engine 69 | const queryEngine = index.asQueryEngine({ 70 | retriever, 71 | }); 72 | 73 | const query = "What is the meaning of life?"; 74 | 75 | // Query 76 | const response = await queryEngine.query({ 77 | query, 78 | }); 79 | 80 | // Log the response 81 | console.log(response.response); 82 | } 83 | ``` 84 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/available_llms/together.md: -------------------------------------------------------------------------------- 1 | # Together LLM 2 | 3 | ## Usage 4 | 5 | ```ts 6 | import { TogetherLLM, serviceContextFromDefaults } from "llamaindex"; 7 | 8 | const togetherLLM = new TogetherLLM({ 9 | apiKey: "", 10 | }); 11 | 12 | const serviceContext = serviceContextFromDefaults({ llm: togetherLLM }); 13 | ``` 14 | 15 | ## Load and index documents 16 | 17 | For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index. 18 | 19 | ```ts 20 | const document = new Document({ text: essay, id_: "essay" }); 21 | 22 | const index = await VectorStoreIndex.fromDocuments([document], { 23 | serviceContext, 24 | }); 25 | ``` 26 | 27 | ## Query 28 | 29 | ```ts 30 | const queryEngine = index.asQueryEngine(); 31 | 32 | const query = "What is the meaning of life?"; 33 | 34 | const results = await queryEngine.query({ 35 | query, 36 | }); 37 | ``` 38 | 39 | ## Full Example 40 | 41 | ```ts 42 | import { 43 | TogetherLLM, 44 | Document, 45 | VectorStoreIndex, 46 | serviceContextFromDefaults, 47 | } from "llamaindex"; 48 | 49 | async function main() { 50 | // Create an instance of the LLM 51 | const togetherLLM = new TogetherLLM({ 52 | apiKey: "", 53 | }); 54 | 55 | // Create a service context 56 | const serviceContext = serviceContextFromDefaults({ llm: togetherLLM }); 57 | 58 | const document = new Document({ text: essay, id_: "essay" }); 59 | 60 | // Load and index documents 61 | const index = await VectorStoreIndex.fromDocuments([document], { 62 | serviceContext, 63 | }); 64 | 65 | // get retriever 66 | const retriever = index.asRetriever(); 67 | 68 | // Create a query engine 69 | const queryEngine = index.asQueryEngine({ 70 | retriever, 71 | }); 72 | 73 | const query = "What is the meaning of life?"; 74 | 75 | // Query 76 | const response = await queryEngine.query({ 77 | query, 78 | }); 79 | 80 | // Log the response 81 | console.log(response.response); 82 | } 83 | ``` 84 | -------------------------------------------------------------------------------- /backend/data/docs/modules/llms/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Large Language Models (LLMs) 6 | 7 | The LLM is responsible for reading text and generating natural language responses to queries. By default, LlamaIndex.TS uses `gpt-3.5-turbo`. 8 | 9 | The LLM can be explicitly set in the `ServiceContext` object. 10 | 11 | ```typescript 12 | import { OpenAI, serviceContextFromDefaults } from "llamaindex"; 13 | 14 | const openaiLLM = new OpenAI({ model: "gpt-3.5-turbo", temperature: 0 }); 15 | 16 | const serviceContext = serviceContextFromDefaults({ llm: openaiLLM }); 17 | ``` 18 | 19 | ## Azure OpenAI 20 | 21 | To use Azure OpenAI, you only need to set a few environment variables. 22 | 23 | For example: 24 | 25 | ``` 26 | export AZURE_OPENAI_KEY="" 27 | export AZURE_OPENAI_ENDPOINT="" 28 | export AZURE_OPENAI_DEPLOYMENT="gpt-4" # or some other deployment name 29 | ``` 30 | 31 | ## Local LLM 32 | 33 | For local LLMs, currently we recommend the use of [Ollama](./available_llms/ollama.md) LLM. 34 | 35 | ## API Reference 36 | 37 | - [OpenAI](../api/classes/OpenAI.md) 38 | - [ServiceContext](../api/interfaces//ServiceContext.md) 39 | -------------------------------------------------------------------------------- /backend/data/docs/modules/node_parser.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # NodeParser 6 | 7 | The `NodeParser` in LlamaIndex is responsible for splitting `Document` objects into more manageable `Node` objects. When you call `.fromDocuments()`, the `NodeParser` from the `ServiceContext` is used to do this automatically for you. Alternatively, you can use it to split documents ahead of time. 8 | 9 | ```typescript 10 | import { Document, SimpleNodeParser } from "llamaindex"; 11 | 12 | const nodeParser = new SimpleNodeParser(); 13 | const nodes = nodeParser.getNodesFromDocuments([ 14 | new Document({ text: "I am 10 years old. John is 20 years old." }), 15 | ]); 16 | ``` 17 | 18 | ## TextSplitter 19 | 20 | The underlying text splitter will split text by sentences. It can also be used as a standalone module for splitting raw text. 21 | 22 | ```typescript 23 | import { SentenceSplitter } from "llamaindex"; 24 | 25 | const splitter = new SentenceSplitter({ chunkSize: 1 }); 26 | 27 | const textSplits = splitter.splitText("Hello World"); 28 | ``` 29 | 30 | ## MarkdownNodeParser 31 | 32 | The `MarkdownNodeParser` is a more advanced `NodeParser` that can handle markdown documents. It will split the markdown into nodes and then parse the nodes into a `Document` object. 33 | 34 | ```typescript 35 | import { MarkdownNodeParser } from "llamaindex"; 36 | 37 | const nodeParser = new MarkdownNodeParser(); 38 | 39 | const nodes = nodeParser.getNodesFromDocuments([ 40 | new Document({ 41 | text: `# Main Header 42 | Main content 43 | 44 | # Header 2 45 | Header 2 content 46 | 47 | ## Sub-header 48 | Sub-header content 49 | 50 | `, 51 | }), 52 | ]); 53 | ``` 54 | 55 | The output metadata will be something like: 56 | 57 | ```bash 58 | [ 59 | TextNode { 60 | id_: '008e41a8-b097-487c-bee8-bd88b9455844', 61 | metadata: { 'Header 1': 'Main Header' }, 62 | excludedEmbedMetadataKeys: [], 63 | excludedLlmMetadataKeys: [], 64 | relationships: { PARENT: [Array] }, 65 | hash: 'KJ5e/um/RkHaNR6bonj9ormtZY7I8i4XBPVYHXv1A5M=', 66 | text: 'Main Header\nMain content', 67 | textTemplate: '', 68 | metadataSeparator: '\n' 69 | }, 70 | TextNode { 71 | id_: '0f5679b3-ba63-4aff-aedc-830c4208d0b5', 72 | metadata: { 'Header 1': 'Header 2' }, 73 | excludedEmbedMetadataKeys: [], 74 | excludedLlmMetadataKeys: [], 75 | relationships: { PARENT: [Array] }, 76 | hash: 'IP/g/dIld3DcbK+uHzDpyeZ9IdOXY4brxhOIe7wc488=', 77 | text: 'Header 2\nHeader 2 content', 78 | textTemplate: '', 79 | metadataSeparator: '\n' 80 | }, 81 | TextNode { 82 | id_: 'e81e9bd0-121c-4ead-8ca7-1639d65fdf90', 83 | metadata: { 'Header 1': 'Header 2', 'Header 2': 'Sub-header' }, 84 | excludedEmbedMetadataKeys: [], 85 | excludedLlmMetadataKeys: [], 86 | relationships: { PARENT: [Array] }, 87 | hash: 'B3kYNnxaYi9ghtAgwza0ZEVKF4MozobkNUlcekDL7JQ=', 88 | text: 'Sub-header\nSub-header content', 89 | textTemplate: '', 90 | metadataSeparator: '\n' 91 | } 92 | ] 93 | ``` 94 | 95 | ## API Reference 96 | 97 | - [SimpleNodeParser](../api/classes/SimpleNodeParser.md) 98 | - [SentenceSplitter](../api/classes/SentenceSplitter.md) 99 | -------------------------------------------------------------------------------- /backend/data/docs/modules/node_postprocessors/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Node Postprocessors" 2 | position: 3 3 | -------------------------------------------------------------------------------- /backend/data/docs/modules/node_postprocessors/cohere_reranker.md: -------------------------------------------------------------------------------- 1 | # Cohere Reranker 2 | 3 | The Cohere Reranker is a postprocessor that uses the Cohere API to rerank the results of a search query. 4 | 5 | ## Setup 6 | 7 | Firstly, you will need to install the `llamaindex` package. 8 | 9 | ```bash 10 | pnpm install llamaindex 11 | ``` 12 | 13 | Now, you will need to sign up for an API key at [Cohere](https://cohere.ai/). Once you have your API key you can import the necessary modules and create a new instance of the `CohereRerank` class. 14 | 15 | ```ts 16 | import { 17 | CohereRerank, 18 | Document, 19 | OpenAI, 20 | VectorStoreIndex, 21 | serviceContextFromDefaults, 22 | } from "llamaindex"; 23 | ``` 24 | 25 | ## Load and index documents 26 | 27 | For this example, we will use a single document. In a real-world scenario, you would have multiple documents to index. 28 | 29 | ```ts 30 | const document = new Document({ text: essay, id_: "essay" }); 31 | 32 | const serviceContext = serviceContextFromDefaults({ 33 | llm: new OpenAI({ model: "gpt-3.5-turbo", temperature: 0.1 }), 34 | }); 35 | 36 | const index = await VectorStoreIndex.fromDocuments([document], { 37 | serviceContext, 38 | }); 39 | ``` 40 | 41 | ## Increase similarity topK to retrieve more results 42 | 43 | The default value for `similarityTopK` is 2. This means that only the most similar document will be returned. To retrieve more results, you can increase the value of `similarityTopK`. 44 | 45 | ```ts 46 | const retriever = index.asRetriever(); 47 | retriever.similarityTopK = 5; 48 | ``` 49 | 50 | ## Create a new instance of the CohereRerank class 51 | 52 | Then you can create a new instance of the `CohereRerank` class and pass in your API key and the number of results you want to return. 53 | 54 | ```ts 55 | const nodePostprocessor = new CohereRerank({ 56 | apiKey: "", 57 | topN: 4, 58 | }); 59 | ``` 60 | 61 | ## Create a query engine with the retriever and node postprocessor 62 | 63 | ```ts 64 | const queryEngine = index.asQueryEngine({ 65 | retriever, 66 | nodePostprocessors: [nodePostprocessor], 67 | }); 68 | 69 | // log the response 70 | const response = await queryEngine.query("Where did the author grown up?"); 71 | ``` 72 | -------------------------------------------------------------------------------- /backend/data/docs/modules/node_postprocessors/index.md: -------------------------------------------------------------------------------- 1 | # Node Postprocessors 2 | 3 | ## Concept 4 | 5 | Node postprocessors are a set of modules that take a set of nodes, and apply some kind of transformation or filtering before returning them. 6 | 7 | In LlamaIndex, node postprocessors are most commonly applied within a query engine, after the node retrieval step and before the response synthesis step. 8 | 9 | LlamaIndex offers several node postprocessors for immediate use, while also providing a simple API for adding your own custom postprocessors. 10 | 11 | ## Usage Pattern 12 | 13 | An example of using a node postprocessors is below: 14 | 15 | ```ts 16 | import { 17 | Node, 18 | NodeWithScore, 19 | SimilarityPostprocessor, 20 | CohereRerank, 21 | } from "llamaindex"; 22 | 23 | const nodes: NodeWithScore[] = [ 24 | { 25 | node: new TextNode({ text: "hello world" }), 26 | score: 0.8, 27 | }, 28 | { 29 | node: new TextNode({ text: "LlamaIndex is the best" }), 30 | score: 0.6, 31 | }, 32 | ]; 33 | 34 | // similarity postprocessor: filter nodes below 0.75 similarity score 35 | const processor = new SimilarityPostprocessor({ 36 | similarityCutoff: 0.7, 37 | }); 38 | 39 | const filteredNodes = processor.postprocessNodes(nodes); 40 | 41 | // cohere rerank: rerank nodes given query using trained model 42 | const reranker = new CohereRerank({ 43 | apiKey: "", 44 | topN: 2, 45 | }); 46 | 47 | const rerankedNodes = await reranker.postprocessNodes(nodes, ""); 48 | 49 | console.log(filteredNodes, rerankedNodes); 50 | ``` 51 | 52 | Now you can use the `filteredNodes` and `rerankedNodes` in your application. 53 | 54 | ## Using Node Postprocessors in LlamaIndex 55 | 56 | Most commonly, node-postprocessors will be used in a query engine, where they are applied to the nodes returned from a retriever, and before the response synthesis step. 57 | 58 | ### Using Node Postprocessors in a Query Engine 59 | 60 | ```ts 61 | import { Node, NodeWithScore, SimilarityPostprocessor, CohereRerank } from "llamaindex"; 62 | 63 | const nodes: NodeWithScore[] = [ 64 | { 65 | node: new TextNode({ text: "hello world" }), 66 | score: 0.8, 67 | }, 68 | { 69 | node: new TextNode({ text: "LlamaIndex is the best" }), 70 | score: 0.6, 71 | } 72 | ]; 73 | 74 | // cohere rerank: rerank nodes given query using trained model 75 | const reranker = new CohereRerank({ 76 | apiKey: ", 77 | topN: 2, 78 | }) 79 | 80 | const document = new Document({ text: "essay", id_: "essay" }); 81 | 82 | const serviceContext = serviceContextFromDefaults({ 83 | llm: new OpenAI({ model: "gpt-3.5-turbo", temperature: 0.1 }), 84 | }); 85 | 86 | const index = await VectorStoreIndex.fromDocuments([document], { 87 | serviceContext, 88 | }); 89 | 90 | const queryEngine = index.asQueryEngine({ 91 | nodePostprocessors: [processor, reranker], 92 | }); 93 | 94 | // all node post-processors will be applied during each query 95 | const response = await queryEngine.query(""); 96 | ``` 97 | 98 | ### Using with retrieved nodes 99 | 100 | ```ts 101 | import { SimilarityPostprocessor } from "llamaindex"; 102 | 103 | nodes = await index.asRetriever().retrieve("test query str"); 104 | 105 | const processor = new SimilarityPostprocessor({ 106 | similarityCutoff: 0.7, 107 | }); 108 | 109 | const filteredNodes = processor.postprocessNodes(nodes); 110 | ``` 111 | -------------------------------------------------------------------------------- /backend/data/docs/modules/prompt/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Prompts" 2 | position: 0 3 | -------------------------------------------------------------------------------- /backend/data/docs/modules/prompt/index.md: -------------------------------------------------------------------------------- 1 | # Prompts 2 | 3 | Prompting is the fundamental input that gives LLMs their expressive power. LlamaIndex uses prompts to build the index, do insertion, perform traversal during querying, and to synthesize the final answer. 4 | 5 | Users may also provide their own prompt templates to further customize the behavior of the framework. The best method for customizing is copying the default prompt from the link above, and using that as the base for any modifications. 6 | 7 | ## Usage Pattern 8 | 9 | Currently, there are two ways to customize prompts in LlamaIndex: 10 | 11 | For both methods, you will need to create an function that overrides the default prompt. 12 | 13 | ```ts 14 | // Define a custom prompt 15 | const newTextQaPrompt: TextQaPrompt = ({ context, query }) => { 16 | return `Context information is below. 17 | --------------------- 18 | ${context} 19 | --------------------- 20 | Given the context information and not prior knowledge, answer the query. 21 | Answer the query in the style of a Sherlock Holmes detective novel. 22 | Query: ${query} 23 | Answer:`; 24 | }; 25 | ``` 26 | 27 | ### 1. Customizing the default prompt on initialization 28 | 29 | The first method is to create a new instance of `ResponseSynthesizer` (or the module you would like to update the prompt) and pass the custom prompt to the `responseBuilder` parameter. Then, pass the instance to the `asQueryEngine` method of the index. 30 | 31 | ```ts 32 | // Create an instance of response synthesizer 33 | const responseSynthesizer = new ResponseSynthesizer({ 34 | responseBuilder: new CompactAndRefine(serviceContext, newTextQaPrompt), 35 | }); 36 | 37 | // Create index 38 | const index = await VectorStoreIndex.fromDocuments([document], { 39 | serviceContext, 40 | }); 41 | 42 | // Query the index 43 | const queryEngine = index.asQueryEngine({ responseSynthesizer }); 44 | 45 | const response = await queryEngine.query({ 46 | query: "What did the author do in college?", 47 | }); 48 | ``` 49 | 50 | ### 2. Customizing submodules prompt 51 | 52 | The second method is that most of the modules in LlamaIndex have a `getPrompts` and a `updatePrompt` method that allows you to override the default prompt. This method is useful when you want to change the prompt on the fly or in submodules on a more granular level. 53 | 54 | ```ts 55 | // Create index 56 | const index = await VectorStoreIndex.fromDocuments([document], { 57 | serviceContext, 58 | }); 59 | 60 | // Query the index 61 | const queryEngine = index.asQueryEngine(); 62 | 63 | // Get a list of prompts for the query engine 64 | const prompts = queryEngine.getPrompts(); 65 | 66 | // output: { "responseSynthesizer:textQATemplate": defaultTextQaPrompt, "responseSynthesizer:refineTemplate": defaultRefineTemplatePrompt } 67 | 68 | // Now, we can override the default prompt 69 | queryEngine.updatePrompt({ 70 | "responseSynthesizer:textQATemplate": newTextQaPrompt, 71 | }); 72 | 73 | const response = await queryEngine.query({ 74 | query: "What did the author do in college?", 75 | }); 76 | ``` 77 | -------------------------------------------------------------------------------- /backend/data/docs/modules/query_engines/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Query Engines" 2 | position: 2 3 | -------------------------------------------------------------------------------- /backend/data/docs/modules/query_engines/index.md: -------------------------------------------------------------------------------- 1 | # QueryEngine 2 | 3 | A query engine wraps a `Retriever` and a `ResponseSynthesizer` into a pipeline, that will use the query string to fetech nodes and then send them to the LLM to generate a response. 4 | 5 | ```typescript 6 | const queryEngine = index.asQueryEngine(); 7 | const response = await queryEngine.query({ query: "query string" }); 8 | ``` 9 | 10 | The `query` function also supports streaming, just add `stream: true` as an option: 11 | 12 | ```typescript 13 | const stream = await queryEngine.query({ query: "query string", stream: true }); 14 | for await (const chunk of stream) { 15 | process.stdout.write(chunk.response); 16 | } 17 | ``` 18 | 19 | ## Sub Question Query Engine 20 | 21 | The basic concept of the Sub Question Query Engine is that it splits a single query into multiple queries, gets an answer for each of those queries, and then combines those different answers into a single coherent response for the user. You can think of it as the "think this through step by step" prompt technique but iterating over your data sources! 22 | 23 | ### Getting Started 24 | 25 | The easiest way to start trying the Sub Question Query Engine is running the subquestion.ts file in [examples](https://github.com/run-llama/LlamaIndexTS/blob/main/examples/subquestion.ts). 26 | 27 | ```bash 28 | npx ts-node subquestion.ts 29 | ``` 30 | 31 | ### Tools 32 | 33 | SubQuestionQueryEngine is implemented with Tools. The basic idea of Tools is that they are executable options for the large language model. In this case, our SubQuestionQueryEngine relies on QueryEngineTool, which as you guessed it is a tool to run queries on a QueryEngine. This allows us to give the model an option to query different documents for different questions for example. You could also imagine that the SubQuestionQueryEngine could use a Tool that searches for something on the web or gets an answer using Wolfram Alpha. 34 | 35 | You can learn more about Tools by taking a look at the LlamaIndex Python documentation https://gpt-index.readthedocs.io/en/latest/core_modules/agent_modules/tools/root.html 36 | 37 | ## API Reference 38 | 39 | - [RetrieverQueryEngine](../../api/classes/RetrieverQueryEngine.md) 40 | - [SubQuestionQueryEngine](../../api/classes/SubQuestionQueryEngine.md) 41 | - [QueryEngineTool](../../api/interfaces/QueryEngineTool.md) 42 | -------------------------------------------------------------------------------- /backend/data/docs/modules/query_engines/metadata_filtering.md: -------------------------------------------------------------------------------- 1 | # Metadata Filtering 2 | 3 | Metadata filtering is a way to filter the documents that are returned by a query based on the metadata associated with the documents. This is useful when you want to filter the documents based on some metadata that is not part of the document text. 4 | 5 | You can also check our multi-tenancy blog post to see how metadata filtering can be used in a multi-tenant environment. [https://blog.llamaindex.ai/building-multi-tenancy-rag-system-with-llamaindex-0d6ab4e0c44b] (the article uses the Python version of LlamaIndex, but the concepts are the same). 6 | 7 | ## Setup 8 | 9 | Firstly if you haven't already, you need to install the `llamaindex` package: 10 | 11 | ```bash 12 | pnpm i llamaindex 13 | ``` 14 | 15 | Then you can import the necessary modules from `llamaindex`: 16 | 17 | ```ts 18 | import { 19 | ChromaVectorStore, 20 | Document, 21 | VectorStoreIndex, 22 | storageContextFromDefaults, 23 | } from "llamaindex"; 24 | 25 | const collectionName = "dog_colors"; 26 | ``` 27 | 28 | ## Creating documents with metadata 29 | 30 | You can create documents with metadata using the `Document` class: 31 | 32 | ```ts 33 | const docs = [ 34 | new Document({ 35 | text: "The dog is brown", 36 | metadata: { 37 | color: "brown", 38 | dogId: "1", 39 | }, 40 | }), 41 | new Document({ 42 | text: "The dog is red", 43 | metadata: { 44 | color: "red", 45 | dogId: "2", 46 | }, 47 | }), 48 | ]; 49 | ``` 50 | 51 | ## Creating a ChromaDB vector store 52 | 53 | You can create a `ChromaVectorStore` to store the documents: 54 | 55 | ```ts 56 | const chromaVS = new ChromaVectorStore({ collectionName }); 57 | const serviceContext = await storageContextFromDefaults({ 58 | vectorStore: chromaVS, 59 | }); 60 | 61 | const index = await VectorStoreIndex.fromDocuments(docs, { 62 | storageContext: serviceContext, 63 | }); 64 | ``` 65 | 66 | ## Querying the index with metadata filtering 67 | 68 | Now you can query the index with metadata filtering using the `preFilters` option: 69 | 70 | ```ts 71 | const queryEngine = index.asQueryEngine({ 72 | preFilters: { 73 | filters: [ 74 | { 75 | key: "dogId", 76 | value: "2", 77 | filterType: "ExactMatch", 78 | }, 79 | ], 80 | }, 81 | }); 82 | 83 | const response = await queryEngine.query({ 84 | query: "What is the color of the dog?", 85 | }); 86 | 87 | console.log(response.toString()); 88 | ``` 89 | 90 | ## Full Code 91 | 92 | ```ts 93 | import { 94 | ChromaVectorStore, 95 | Document, 96 | VectorStoreIndex, 97 | storageContextFromDefaults, 98 | } from "llamaindex"; 99 | 100 | const collectionName = "dog_colors"; 101 | 102 | async function main() { 103 | try { 104 | const docs = [ 105 | new Document({ 106 | text: "The dog is brown", 107 | metadata: { 108 | color: "brown", 109 | dogId: "1", 110 | }, 111 | }), 112 | new Document({ 113 | text: "The dog is red", 114 | metadata: { 115 | color: "red", 116 | dogId: "2", 117 | }, 118 | }), 119 | ]; 120 | 121 | console.log("Creating ChromaDB vector store"); 122 | const chromaVS = new ChromaVectorStore({ collectionName }); 123 | const ctx = await storageContextFromDefaults({ vectorStore: chromaVS }); 124 | 125 | console.log("Embedding documents and adding to index"); 126 | const index = await VectorStoreIndex.fromDocuments(docs, { 127 | storageContext: ctx, 128 | }); 129 | 130 | console.log("Querying index"); 131 | const queryEngine = index.asQueryEngine({ 132 | preFilters: { 133 | filters: [ 134 | { 135 | key: "dogId", 136 | value: "2", 137 | filterType: "ExactMatch", 138 | }, 139 | ], 140 | }, 141 | }); 142 | const response = await queryEngine.query({ 143 | query: "What is the color of the dog?", 144 | }); 145 | console.log(response.toString()); 146 | } catch (e) { 147 | console.error(e); 148 | } 149 | } 150 | 151 | main(); 152 | ``` 153 | -------------------------------------------------------------------------------- /backend/data/docs/modules/query_engines/router_query_engine.md: -------------------------------------------------------------------------------- 1 | # Router Query Engine 2 | 3 | In this tutorial, we define a custom router query engine that selects one out of several candidate query engines to execute a query. 4 | 5 | ## Setup 6 | 7 | First, we need to install import the necessary modules from `llamaindex`: 8 | 9 | ```bash 10 | pnpm i lamaindex 11 | ``` 12 | 13 | ```ts 14 | import { 15 | OpenAI, 16 | RouterQueryEngine, 17 | SimpleDirectoryReader, 18 | SimpleNodeParser, 19 | SummaryIndex, 20 | VectorStoreIndex, 21 | serviceContextFromDefaults, 22 | } from "llamaindex"; 23 | ``` 24 | 25 | ## Loading Data 26 | 27 | Next, we need to load some data. We will use the `SimpleDirectoryReader` to load documents from a directory: 28 | 29 | ```ts 30 | const documents = await new SimpleDirectoryReader().loadData({ 31 | directoryPath: "node_modules/llamaindex/examples", 32 | }); 33 | ``` 34 | 35 | ## Service Context 36 | 37 | Next, we need to define some basic rules and parse the documents into nodes. We will use the `SimpleNodeParser` to parse the documents into nodes and `ServiceContext` to define the rules (eg. LLM API key, chunk size, etc.): 38 | 39 | ```ts 40 | const nodeParser = new SimpleNodeParser({ 41 | chunkSize: 1024, 42 | }); 43 | 44 | const serviceContext = serviceContextFromDefaults({ 45 | nodeParser, 46 | llm: new OpenAI(), 47 | }); 48 | ``` 49 | 50 | ## Creating Indices 51 | 52 | Next, we need to create some indices. We will create a `VectorStoreIndex` and a `SummaryIndex`: 53 | 54 | ```ts 55 | const vectorIndex = await VectorStoreIndex.fromDocuments(documents, { 56 | serviceContext, 57 | }); 58 | 59 | const summaryIndex = await SummaryIndex.fromDocuments(documents, { 60 | serviceContext, 61 | }); 62 | ``` 63 | 64 | ## Creating Query Engines 65 | 66 | Next, we need to create some query engines. We will create a `VectorStoreQueryEngine` and a `SummaryQueryEngine`: 67 | 68 | ```ts 69 | const vectorQueryEngine = vectorIndex.asQueryEngine(); 70 | const summaryQueryEngine = summaryIndex.asQueryEngine(); 71 | ``` 72 | 73 | ## Creating a Router Query Engine 74 | 75 | Next, we need to create a router query engine. We will use the `RouterQueryEngine` to create a router query engine: 76 | 77 | We're defining two query engines, one for summarization and one for retrieving specific context. The router query engine will select the most appropriate query engine based on the query. 78 | 79 | ```ts 80 | const queryEngine = RouterQueryEngine.fromDefaults({ 81 | queryEngineTools: [ 82 | { 83 | queryEngine: vectorQueryEngine, 84 | description: "Useful for summarization questions related to Abramov", 85 | }, 86 | { 87 | queryEngine: summaryQueryEngine, 88 | description: "Useful for retrieving specific context from Abramov", 89 | }, 90 | ], 91 | serviceContext, 92 | }); 93 | ``` 94 | 95 | ## Querying the Router Query Engine 96 | 97 | Finally, we can query the router query engine: 98 | 99 | ```ts 100 | const summaryResponse = await queryEngine.query({ 101 | query: "Give me a summary about his past experiences?", 102 | }); 103 | 104 | console.log({ 105 | answer: summaryResponse.response, 106 | metadata: summaryResponse?.metadata?.selectorResult, 107 | }); 108 | ``` 109 | 110 | ## Full code 111 | 112 | ```ts 113 | import { 114 | OpenAI, 115 | RouterQueryEngine, 116 | SimpleDirectoryReader, 117 | SimpleNodeParser, 118 | SummaryIndex, 119 | VectorStoreIndex, 120 | serviceContextFromDefaults, 121 | } from "llamaindex"; 122 | 123 | async function main() { 124 | // Load documents from a directory 125 | const documents = await new SimpleDirectoryReader().loadData({ 126 | directoryPath: "node_modules/llamaindex/examples", 127 | }); 128 | 129 | // Parse the documents into nodes 130 | const nodeParser = new SimpleNodeParser({ 131 | chunkSize: 1024, 132 | }); 133 | 134 | // Create a service context 135 | const serviceContext = serviceContextFromDefaults({ 136 | nodeParser, 137 | llm: new OpenAI(), 138 | }); 139 | 140 | // Create indices 141 | const vectorIndex = await VectorStoreIndex.fromDocuments(documents, { 142 | serviceContext, 143 | }); 144 | 145 | const summaryIndex = await SummaryIndex.fromDocuments(documents, { 146 | serviceContext, 147 | }); 148 | 149 | // Create query engines 150 | const vectorQueryEngine = vectorIndex.asQueryEngine(); 151 | const summaryQueryEngine = summaryIndex.asQueryEngine(); 152 | 153 | // Create a router query engine 154 | const queryEngine = RouterQueryEngine.fromDefaults({ 155 | queryEngineTools: [ 156 | { 157 | queryEngine: vectorQueryEngine, 158 | description: "Useful for summarization questions related to Abramov", 159 | }, 160 | { 161 | queryEngine: summaryQueryEngine, 162 | description: "Useful for retrieving specific context from Abramov", 163 | }, 164 | ], 165 | serviceContext, 166 | }); 167 | 168 | // Query the router query engine 169 | const summaryResponse = await queryEngine.query({ 170 | query: "Give me a summary about his past experiences?", 171 | }); 172 | 173 | console.log({ 174 | answer: summaryResponse.response, 175 | metadata: summaryResponse?.metadata?.selectorResult, 176 | }); 177 | 178 | const specificResponse = await queryEngine.query({ 179 | query: "Tell me about abramov first job?", 180 | }); 181 | 182 | console.log({ 183 | answer: specificResponse.response, 184 | metadata: specificResponse.metadata.selectorResult, 185 | }); 186 | } 187 | 188 | main().then(() => console.log("Done")); 189 | ``` 190 | -------------------------------------------------------------------------------- /backend/data/docs/modules/response_synthesizer.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # ResponseSynthesizer 6 | 7 | The ResponseSynthesizer is responsible for sending the query, nodes, and prompt templates to the LLM to generate a response. There are a few key modes for generating a response: 8 | 9 | - `Refine`: "create and refine" an answer by sequentially going through each retrieved text chunk. 10 | This makes a separate LLM call per Node. Good for more detailed answers. 11 | - `CompactAndRefine` (default): "compact" the prompt during each LLM call by stuffing as 12 | many text chunks that can fit within the maximum prompt size. If there are 13 | too many chunks to stuff in one prompt, "create and refine" an answer by going through 14 | multiple compact prompts. The same as `refine`, but should result in less LLM calls. 15 | - `TreeSummarize`: Given a set of text chunks and the query, recursively construct a tree 16 | and return the root node as the response. Good for summarization purposes. 17 | - `SimpleResponseBuilder`: Given a set of text chunks and the query, apply the query to each text 18 | chunk while accumulating the responses into an array. Returns a concatenated string of all 19 | responses. Good for when you need to run the same query separately against each text 20 | chunk. 21 | 22 | ```typescript 23 | import { NodeWithScore, ResponseSynthesizer, TextNode } from "llamaindex"; 24 | 25 | const responseSynthesizer = new ResponseSynthesizer(); 26 | 27 | const nodesWithScore: NodeWithScore[] = [ 28 | { 29 | node: new TextNode({ text: "I am 10 years old." }), 30 | score: 1, 31 | }, 32 | { 33 | node: new TextNode({ text: "John is 20 years old." }), 34 | score: 0.5, 35 | }, 36 | ]; 37 | 38 | const response = await responseSynthesizer.synthesize({ 39 | query: "What age am I?", 40 | nodesWithScore, 41 | }); 42 | console.log(response.response); 43 | ``` 44 | 45 | The `synthesize` function also supports streaming, just add `stream: true` as an option: 46 | 47 | ```typescript 48 | const stream = await responseSynthesizer.synthesize({ 49 | query: "What age am I?", 50 | nodesWithScore, 51 | stream: true, 52 | }); 53 | for await (const chunk of stream) { 54 | process.stdout.write(chunk.response); 55 | } 56 | ``` 57 | 58 | ## API Reference 59 | 60 | - [ResponseSynthesizer](../api/classes/ResponseSynthesizer.md) 61 | - [Refine](../api/classes/Refine.md) 62 | - [CompactAndRefine](../api/classes/CompactAndRefine.md) 63 | - [TreeSummarize](../api/classes/TreeSummarize.md) 64 | - [SimpleResponseBuilder](../api/classes/SimpleResponseBuilder.md) 65 | -------------------------------------------------------------------------------- /backend/data/docs/modules/retriever.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Retriever 6 | 7 | A retriever in LlamaIndex is what is used to fetch `Node`s from an index using a query string. Aa `VectorIndexRetriever` will fetch the top-k most similar nodes. Meanwhile, a `SummaryIndexRetriever` will fetch all nodes no matter the query. 8 | 9 | ```typescript 10 | const retriever = vector_index.asRetriever(); 11 | retriever.similarityTopK = 3; 12 | 13 | // Fetch nodes! 14 | const nodesWithScore = await retriever.retrieve("query string"); 15 | ``` 16 | 17 | ## API Reference 18 | 19 | - [SummaryIndexRetriever](../api/classes/SummaryIndexRetriever.md) 20 | - [SummaryIndexLLMRetriever](../api/classes/SummaryIndexLLMRetriever.md) 21 | - [VectorIndexRetriever](../api/classes/VectorIndexRetriever.md) 22 | -------------------------------------------------------------------------------- /backend/data/docs/modules/storage.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | --- 4 | 5 | # Storage 6 | 7 | Storage in LlamaIndex.TS works automatically once you've configured a `StorageContext` object. Just configure the `persistDir` and attach it to an index. 8 | 9 | Right now, only saving and loading from disk is supported, with future integrations planned! 10 | 11 | ```typescript 12 | import { Document, VectorStoreIndex, storageContextFromDefaults } from "./src"; 13 | 14 | const storageContext = await storageContextFromDefaults({ 15 | persistDir: "./storage", 16 | }); 17 | 18 | const document = new Document({ text: "Test Text" }); 19 | const index = await VectorStoreIndex.fromDocuments([document], { 20 | storageContext, 21 | }); 22 | ``` 23 | 24 | ## API Reference 25 | 26 | - [StorageContext](../api/interfaces//StorageContext.md) 27 | -------------------------------------------------------------------------------- /backend/data/docs/modules/vector_stores/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Vector Stores" 2 | position: 1 3 | -------------------------------------------------------------------------------- /backend/data/docs/modules/vector_stores/qdrant.md: -------------------------------------------------------------------------------- 1 | # Qdrant Vector Store 2 | 3 | To run this example, you need to have a Qdrant instance running. You can run it with Docker: 4 | 5 | ```bash 6 | docker pull qdrant/qdrant 7 | docker run -p 6333:6333 qdrant/qdrant 8 | ``` 9 | 10 | ## Importing the modules 11 | 12 | ```ts 13 | import fs from "node:fs/promises"; 14 | import { Document, VectorStoreIndex, QdrantVectorStore } from "llamaindex"; 15 | ``` 16 | 17 | ## Load the documents 18 | 19 | ```ts 20 | const path = "node_modules/llamaindex/examples/abramov.txt"; 21 | const essay = await fs.readFile(path, "utf-8"); 22 | ``` 23 | 24 | ## Setup Qdrant 25 | 26 | ```ts 27 | const vectorStore = new QdrantVectorStore({ 28 | url: "http://localhost:6333", 29 | }); 30 | ``` 31 | 32 | ## Setup the index 33 | 34 | ```ts 35 | const document = new Document({ text: essay, id_: path }); 36 | 37 | const index = await VectorStoreIndex.fromDocuments([document], { 38 | vectorStore, 39 | }); 40 | ``` 41 | 42 | ## Query the index 43 | 44 | ```ts 45 | const queryEngine = index.asQueryEngine(); 46 | 47 | const response = await queryEngine.query({ 48 | query: "What did the author do in college?", 49 | }); 50 | 51 | // Output response 52 | console.log(response.toString()); 53 | ``` 54 | 55 | ## Full code 56 | 57 | ```ts 58 | import fs from "node:fs/promises"; 59 | import { Document, VectorStoreIndex, QdrantVectorStore } from "llamaindex"; 60 | 61 | async function main() { 62 | const path = "node_modules/llamaindex/examples/abramov.txt"; 63 | const essay = await fs.readFile(path, "utf-8"); 64 | 65 | const vectorStore = new QdrantVectorStore({ 66 | url: "http://localhost:6333", 67 | }); 68 | 69 | const document = new Document({ text: essay, id_: path }); 70 | 71 | const index = await VectorStoreIndex.fromDocuments([document], { 72 | vectorStore, 73 | }); 74 | 75 | const queryEngine = index.asQueryEngine(); 76 | 77 | const response = await queryEngine.query({ 78 | query: "What did the author do in college?", 79 | }); 80 | 81 | // Output response 82 | console.log(response.toString()); 83 | } 84 | 85 | main().catch(console.error); 86 | ``` 87 | -------------------------------------------------------------------------------- /backend/data/docs/observability/_category_.yml: -------------------------------------------------------------------------------- 1 | label: Observability 2 | position: 5 3 | -------------------------------------------------------------------------------- /backend/data/docs/observability/index.md: -------------------------------------------------------------------------------- 1 | # Observability 2 | 3 | LlamaIndex provides **one-click observability** 🔭 to allow you to build principled LLM applications in a production setting. 4 | 5 | A key requirement for principled development of LLM applications over your data (RAG systems, agents) is being able to observe, debug, and evaluate 6 | your system - both as a whole and for each component. 7 | 8 | This feature allows you to seamlessly integrate the LlamaIndex library with powerful observability/evaluation tools offered by our partners. 9 | Configure a variable once, and you'll be able to do things like the following: 10 | 11 | - View LLM/prompt inputs/outputs 12 | - Ensure that the outputs of any component (LLMs, embeddings) are performing as expected 13 | - View call traces for both indexing and querying 14 | 15 | Each provider has similarities and differences. Take a look below for the full set of guides for each one! 16 | 17 | ## OpenLLMetry 18 | 19 | [OpenLLMetry](https://github.com/traceloop/openllmetry-js) is an open-source project based on OpenTelemetry for tracing and monitoring 20 | LLM applications. It connects to [all major observability platforms](https://www.traceloop.com/docs/openllmetry/integrations/introduction) and installs in minutes. 21 | 22 | ### Usage Pattern 23 | 24 | ```bash 25 | npm install @traceloop/node-server-sdk 26 | ``` 27 | 28 | ```js 29 | import * as traceloop from "@traceloop/node-server-sdk"; 30 | 31 | traceloop.initialize({ 32 | apiKey: process.env.TRACELOOP_API_KEY, 33 | disableBatch: true, 34 | }); 35 | ``` 36 | -------------------------------------------------------------------------------- /backend/main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import uvicorn 4 | from app.api.routers.chat import chat_router 5 | from fastapi import FastAPI 6 | from fastapi.middleware.cors import CORSMiddleware 7 | from dotenv import load_dotenv 8 | 9 | load_dotenv() 10 | 11 | app = FastAPI() 12 | 13 | environment = os.getenv("ENVIRONMENT", "dev") # Default to 'development' if not set 14 | 15 | 16 | if environment == "dev": 17 | logger = logging.getLogger("uvicorn") 18 | logger.warning("Running in development mode - allowing CORS for all origins") 19 | app.add_middleware( 20 | CORSMiddleware, 21 | allow_origins=["*"], 22 | allow_credentials=True, 23 | allow_methods=["*"], 24 | allow_headers=["*"], 25 | ) 26 | 27 | app.include_router(chat_router, prefix="/api/chat") 28 | 29 | 30 | if __name__ == "__main__": 31 | uvicorn.run(app="main:app", host="0.0.0.0", reload=True) 32 | -------------------------------------------------------------------------------- /backend/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "app" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Marcus Schiesser "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.11,<3.12" 10 | fastapi = "^0.104.1" 11 | uvicorn = { extras = ["standard"], version = "^0.23.2" } 12 | python-dotenv = "^1.0.0" 13 | unstructured = "0.10.30" 14 | Jinja2 = "3.1.2" 15 | llama-index-core = "^0.10.18.post1" 16 | llama-index-embeddings-openai = "^0.1.6" 17 | llama-index-llms-openai = "^0.1.7" 18 | llama-index-readers-file = "^0.1.8" 19 | llama-index-agent-openai = "^0.1.5" 20 | 21 | 22 | [build-system] 23 | requires = ["poetry-core"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /backend/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsrohan99/llamaindex-docs-agent/1b2a6f5efee1bd5a4b9cd7771eac5f76a57155b1/backend/tests/__init__.py -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [LlamaIndex](https://www.llamaindex.ai/) project using [Next.js](https://nextjs.org/) bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama). 2 | 3 | ## Getting Started 4 | 5 | First, install the dependencies: 6 | 7 | ``` 8 | npm install 9 | ``` 10 | 11 | Second, run the development server: 12 | 13 | ``` 14 | npm run dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about LlamaIndex, take a look at the following resources: 26 | 27 | - [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex (Python features). 28 | - [LlamaIndexTS Documentation](https://ts.llamaindex.ai) - learn about LlamaIndex (Typescript features). 29 | 30 | You can check out [the LlamaIndexTS GitHub repository](https://github.com/run-llama/LlamaIndexTS) - your feedback and contributions are welcome! 31 | -------------------------------------------------------------------------------- /frontend/app/components/chat-section.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Message, useChat } from "ai/react"; 4 | import { ChatInput, ChatMessages } from "./ui/chat"; 5 | import { useMemo } from "react"; 6 | import { transformMessages } from "./transform"; 7 | import useNodes from "../hooks/useNodes"; 8 | import { NodePreview } from "./ui/nodes"; 9 | 10 | export default function ChatSection() { 11 | const { 12 | messages, 13 | input, 14 | isLoading, 15 | handleSubmit, 16 | handleInputChange, 17 | reload, 18 | stop, 19 | } = useChat({ 20 | api: process.env.NEXT_PUBLIC_CHAT_API, 21 | }); 22 | 23 | const { nodes, setNodes } = useNodes(); 24 | 25 | const mergeFunctionMessages = (messages: Message[]): Message[] => { 26 | // only allow the last function message to be shown 27 | return messages.filter( 28 | (msg, i) => msg.role !== "function" || i === messages.length - 1 29 | ); 30 | }; 31 | 32 | const transformedMessages = useMemo(() => { 33 | // return mergeFunctionMessages(transformMessages(messages)); 34 | return transformMessages(messages, setNodes); 35 | }, [messages]); 36 | 37 | return ( 38 |
39 |
40 |
41 | 47 | 53 |
54 | {nodes.length === 0 ? ( 55 | <> 56 | ) : ( 57 |
58 | 59 |
60 | )} 61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /frontend/app/components/header.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | export default function Header() { 4 | return ( 5 |
6 |

7 | Multi-Document Agent based Bot
8 |

9 |

10 | Ask questions over LlamaIndex.TS documentation! 11 |

12 | 28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /frontend/app/components/transform.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "ai"; 2 | import { Message } from "ai/react"; 3 | import { Node } from "./ui/chat/chat.interface"; 4 | 5 | const parseMessageFromToken = ( 6 | tokenString: string, 7 | setNodes: (nodes: Node[]) => void 8 | ): Message => { 9 | try { 10 | const token = JSON.parse(tokenString); 11 | // console.log(token.type); 12 | if (typeof token === "string") { 13 | return { 14 | id: nanoid(), 15 | role: "assistant", 16 | content: token, 17 | }; 18 | } 19 | 20 | const payload = token.payload; 21 | if (token.type === "function_call") { 22 | return { 23 | id: nanoid(), 24 | role: "function", 25 | function_call: { 26 | name: payload.tool_str, 27 | arguments: payload.arguments_str, 28 | }, 29 | content: `Used intermediate tool: ${payload.tool_str} with args: ${payload.arguments_str}`, 30 | }; 31 | } 32 | 33 | // if (token.type === "function_call_response") { 34 | // // return; 35 | // return { 36 | // id: nanoid(), 37 | // role: "function", 38 | // content: `Got output: ${payload.response}`, 39 | // }; 40 | // } 41 | 42 | if (token.type === "nodes_retrieved") { 43 | const nodes = payload.nodes as Node[]; 44 | if (nodes.length !== 0) { 45 | setNodes(nodes); 46 | // console.log(payload.nodes); 47 | // console.log("here"); 48 | } 49 | return { 50 | id: nanoid(), 51 | role: "assistant", 52 | content: "", 53 | }; 54 | } 55 | 56 | return { 57 | id: nanoid(), 58 | role: "assistant", 59 | content: tokenString, 60 | }; 61 | } catch (e) { 62 | console.log(e); 63 | return { 64 | id: nanoid(), 65 | role: "assistant", 66 | content: tokenString, 67 | }; 68 | } 69 | }; 70 | 71 | const mergeLastAssistantMessages = (messages: Message[]): Message[] => { 72 | const lastMessage = messages[messages.length - 1]; 73 | if (lastMessage?.role !== "assistant") return messages; 74 | 75 | let mergedContent = ""; 76 | let i = messages.length - 1; 77 | 78 | // merge content of last assistant messages 79 | for (; i >= 0; i--) { 80 | if (messages[i].role !== "assistant") { 81 | break; 82 | } 83 | mergedContent = messages[i].content + mergedContent; 84 | } 85 | 86 | return [ 87 | ...messages.slice(0, i + 1), 88 | { 89 | id: nanoid(), 90 | role: "assistant", 91 | content: mergedContent, 92 | }, 93 | ]; 94 | }; 95 | 96 | const extractDataTokens = (messageContent: string): string[] => { 97 | const regex = /data: (.+?)\n+/g; 98 | const matches = []; 99 | let match; 100 | while ((match = regex.exec(messageContent)) !== null) { 101 | matches.push(match[1]); 102 | } 103 | return matches; 104 | }; 105 | 106 | const transformMessage = ( 107 | message: Message, 108 | setNodes: (nodes: Node[]) => void 109 | ): Message[] => { 110 | if (message.role !== "assistant") { 111 | // If the message is not from the assistant, return it as is 112 | return [message]; 113 | } 114 | // Split the message content into an array of data tokens 115 | const dataTokens = extractDataTokens(message.content); 116 | 117 | // Extract messages from data tokens 118 | const messages = dataTokens.map((dataToken) => 119 | parseMessageFromToken(dataToken, setNodes) 120 | ); 121 | 122 | // Merge last assistant messages to one 123 | return mergeLastAssistantMessages(messages); 124 | }; 125 | 126 | export const transformMessages = ( 127 | messages: Message[], 128 | setNodes: (nodes: Node[]) => void 129 | ) => { 130 | return messages.flatMap((message) => transformMessage(message, setNodes)); 131 | }; 132 | -------------------------------------------------------------------------------- /frontend/app/components/ui/README.md: -------------------------------------------------------------------------------- 1 | Using the chat component from https://github.com/marcusschiesser/ui (based on https://ui.shadcn.com/) 2 | -------------------------------------------------------------------------------- /frontend/app/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "@radix-ui/react-slot"; 2 | import { cva, type VariantProps } from "class-variance-authority"; 3 | import * as React from "react"; 4 | 5 | import { cn } from "./lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | }, 34 | ); 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean; 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button"; 45 | return ( 46 | 51 | ); 52 | }, 53 | ); 54 | Button.displayName = "Button"; 55 | 56 | export { Button, buttonVariants }; 57 | -------------------------------------------------------------------------------- /frontend/app/components/ui/chat/chat-actions.tsx: -------------------------------------------------------------------------------- 1 | import { PauseCircle, RefreshCw } from "lucide-react"; 2 | 3 | import { Button } from "../button"; 4 | import { ChatHandler } from "./chat.interface"; 5 | 6 | export default function ChatActions( 7 | props: Pick & { 8 | showReload?: boolean; 9 | showStop?: boolean; 10 | }, 11 | ) { 12 | return ( 13 |
14 | {props.showStop && ( 15 | 19 | )} 20 | {props.showReload && ( 21 | 25 | )} 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /frontend/app/components/ui/chat/chat-avatar.tsx: -------------------------------------------------------------------------------- 1 | import { Shell, User2 } from "lucide-react"; 2 | import Image from "next/image"; 3 | 4 | export default function ChatAvatar({ role }: { role: string }) { 5 | if (role === "user") { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | 13 | if (role === "function") { 14 | return ( 15 |
16 | 17 |
18 | ); 19 | } 20 | 21 | return ( 22 |
23 | Llama Logo 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /frontend/app/components/ui/chat/chat-input.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "../button"; 2 | import { Input } from "../input"; 3 | import { ChatHandler } from "./chat.interface"; 4 | 5 | export default function ChatInput( 6 | props: Pick< 7 | ChatHandler, 8 | "isLoading" | "handleSubmit" | "handleInputChange" | "input" 9 | >, 10 | ) { 11 | return ( 12 |
16 | 24 | 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /frontend/app/components/ui/chat/chat-message.tsx: -------------------------------------------------------------------------------- 1 | import { Check, Copy } from "lucide-react"; 2 | 3 | import { Button } from "../button"; 4 | import ChatAvatar from "./chat-avatar"; 5 | import { Message } from "./chat.interface"; 6 | import Markdown from "./markdown"; 7 | import { useCopyToClipboard } from "./use-copy-to-clipboard"; 8 | import { cn } from "../lib/utils"; 9 | 10 | export default function ChatMessage(chatMessage: Message) { 11 | const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }); 12 | return ( 13 |
14 | 15 |
16 |
22 | 23 |
24 | 36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /frontend/app/components/ui/chat/chat-messages.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | import ChatActions from "./chat-actions"; 4 | import ChatMessage from "./chat-message"; 5 | import { ChatHandler } from "./chat.interface"; 6 | import { Loader2 } from "lucide-react"; 7 | import { NodePreview } from "../nodes"; 8 | 9 | export default function ChatMessages( 10 | props: Pick 11 | ) { 12 | const scrollableChatContainerRef = useRef(null); 13 | const messageLength = props.messages.length; 14 | const lastMessage = props.messages[messageLength - 1]; 15 | 16 | const scrollToBottom = () => { 17 | if (scrollableChatContainerRef.current) { 18 | scrollableChatContainerRef.current.scrollTop = 19 | scrollableChatContainerRef.current.scrollHeight; 20 | } 21 | }; 22 | 23 | const isLastMessageFromAssistant = 24 | messageLength > 0 && lastMessage?.role !== "user"; 25 | const showReload = 26 | props.reload && !props.isLoading && isLastMessageFromAssistant; 27 | const showStop = props.stop && props.isLoading; 28 | 29 | // `isPending` indicate 30 | // that stream response is not yet received from the server, 31 | // so we show a loading indicator to give a better UX. 32 | const isPending = props.isLoading && !isLastMessageFromAssistant; 33 | 34 | useEffect(() => { 35 | scrollToBottom(); 36 | }, [messageLength, lastMessage]); 37 | 38 | return ( 39 |
40 |
44 | {props.messages.map((m) => { 45 | if (m.content) { 46 | return ; 47 | } 48 | })} 49 | {isPending && ( 50 |
51 | 52 |
53 | )} 54 |
55 |
56 | 62 |
63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /frontend/app/components/ui/chat/chat.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Message { 2 | id: string; 3 | content: string; 4 | role: string; 5 | } 6 | 7 | export interface ChatHandler { 8 | messages: Message[]; 9 | input: string; 10 | isLoading: boolean; 11 | handleSubmit: (e: React.FormEvent) => void; 12 | handleInputChange: (e: React.ChangeEvent) => void; 13 | reload?: () => void; 14 | stop?: () => void; 15 | } 16 | 17 | export interface Node { 18 | id: string; 19 | title: string; 20 | url: string; 21 | section: string; 22 | summary: string; 23 | } 24 | -------------------------------------------------------------------------------- /frontend/app/components/ui/chat/codeblock.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React, { FC, memo } from "react" 4 | import { Check, Copy, Download } from "lucide-react" 5 | import { Prism, SyntaxHighlighterProps } from "react-syntax-highlighter" 6 | import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism" 7 | 8 | import { Button } from "../button" 9 | import { useCopyToClipboard } from "./use-copy-to-clipboard" 10 | 11 | // TODO: Remove this when @type/react-syntax-highlighter is updated 12 | const SyntaxHighlighter = Prism as unknown as FC 13 | 14 | interface Props { 15 | language: string 16 | value: string 17 | } 18 | 19 | interface languageMap { 20 | [key: string]: string | undefined 21 | } 22 | 23 | export const programmingLanguages: languageMap = { 24 | javascript: ".js", 25 | python: ".py", 26 | java: ".java", 27 | c: ".c", 28 | cpp: ".cpp", 29 | "c++": ".cpp", 30 | "c#": ".cs", 31 | ruby: ".rb", 32 | php: ".php", 33 | swift: ".swift", 34 | "objective-c": ".m", 35 | kotlin: ".kt", 36 | typescript: ".ts", 37 | go: ".go", 38 | perl: ".pl", 39 | rust: ".rs", 40 | scala: ".scala", 41 | haskell: ".hs", 42 | lua: ".lua", 43 | shell: ".sh", 44 | sql: ".sql", 45 | html: ".html", 46 | css: ".css", 47 | // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component 48 | } 49 | 50 | export const generateRandomString = (length: number, lowercase = false) => { 51 | const chars = "ABCDEFGHJKLMNPQRSTUVWXY3456789" // excluding similar looking characters like Z, 2, I, 1, O, 0 52 | let result = "" 53 | for (let i = 0; i < length; i++) { 54 | result += chars.charAt(Math.floor(Math.random() * chars.length)) 55 | } 56 | return lowercase ? result.toLowerCase() : result 57 | } 58 | 59 | const CodeBlock: FC = memo(({ language, value }) => { 60 | const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }) 61 | 62 | const downloadAsFile = () => { 63 | if (typeof window === "undefined") { 64 | return 65 | } 66 | const fileExtension = programmingLanguages[language] || ".file" 67 | const suggestedFileName = `file-${generateRandomString( 68 | 3, 69 | true 70 | )}${fileExtension}` 71 | const fileName = window.prompt("Enter file name" || "", suggestedFileName) 72 | 73 | if (!fileName) { 74 | // User pressed cancel on prompt. 75 | return 76 | } 77 | 78 | const blob = new Blob([value], { type: "text/plain" }) 79 | const url = URL.createObjectURL(blob) 80 | const link = document.createElement("a") 81 | link.download = fileName 82 | link.href = url 83 | link.style.display = "none" 84 | document.body.appendChild(link) 85 | link.click() 86 | document.body.removeChild(link) 87 | URL.revokeObjectURL(url) 88 | } 89 | 90 | const onCopy = () => { 91 | if (isCopied) return 92 | copyToClipboard(value) 93 | } 94 | 95 | return ( 96 |
97 |
98 | {language} 99 |
100 | 104 | 112 |
113 |
114 | 132 | {value} 133 | 134 |
135 | ) 136 | }) 137 | CodeBlock.displayName = "CodeBlock" 138 | 139 | export { CodeBlock } 140 | -------------------------------------------------------------------------------- /frontend/app/components/ui/chat/index.ts: -------------------------------------------------------------------------------- 1 | import ChatInput from "./chat-input"; 2 | import ChatMessages from "./chat-messages"; 3 | 4 | export { type ChatHandler, type Message } from "./chat.interface"; 5 | export { ChatMessages, ChatInput }; 6 | -------------------------------------------------------------------------------- /frontend/app/components/ui/chat/markdown.tsx: -------------------------------------------------------------------------------- 1 | import { FC, memo } from "react" 2 | import ReactMarkdown, { Options } from "react-markdown" 3 | import remarkGfm from "remark-gfm" 4 | import remarkMath from "remark-math" 5 | 6 | import { CodeBlock } from "./codeblock" 7 | 8 | const MemoizedReactMarkdown: FC = memo( 9 | ReactMarkdown, 10 | (prevProps, nextProps) => 11 | prevProps.children === nextProps.children && 12 | prevProps.className === nextProps.className 13 | ) 14 | 15 | export default function Markdown({ content }: { content: string }) { 16 | return ( 17 | {children}

23 | }, 24 | code({ node, inline, className, children, ...props }) { 25 | if (children.length) { 26 | if (children[0] == "▍") { 27 | return ( 28 | 29 | ) 30 | } 31 | 32 | children[0] = (children[0] as string).replace("`▍`", "▍") 33 | } 34 | 35 | const match = /language-(\w+)/.exec(className || "") 36 | 37 | if (inline) { 38 | return ( 39 | 40 | {children} 41 | 42 | ) 43 | } 44 | 45 | return ( 46 | 52 | ) 53 | }, 54 | }} 55 | > 56 | {content} 57 |
58 | ) 59 | } 60 | -------------------------------------------------------------------------------- /frontend/app/components/ui/chat/use-copy-to-clipboard.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | 5 | export interface useCopyToClipboardProps { 6 | timeout?: number 7 | } 8 | 9 | export function useCopyToClipboard({ 10 | timeout = 2000 11 | }: useCopyToClipboardProps) { 12 | const [isCopied, setIsCopied] = React.useState(false) 13 | 14 | const copyToClipboard = (value: string) => { 15 | if (typeof window === 'undefined' || !navigator.clipboard?.writeText) { 16 | return 17 | } 18 | 19 | if (!value) { 20 | return 21 | } 22 | 23 | navigator.clipboard.writeText(value).then(() => { 24 | setIsCopied(true) 25 | 26 | setTimeout(() => { 27 | setIsCopied(false) 28 | }, timeout) 29 | }) 30 | } 31 | 32 | return { isCopied, copyToClipboard } 33 | } 34 | -------------------------------------------------------------------------------- /frontend/app/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "./lib/utils"; 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ); 21 | }, 22 | ); 23 | Input.displayName = "Input"; 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /frontend/app/components/ui/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } -------------------------------------------------------------------------------- /frontend/app/components/ui/nodes.tsx: -------------------------------------------------------------------------------- 1 | import { Node } from "./chat/chat.interface"; 2 | import { useEffect, useState } from "react"; 3 | 4 | interface NodeBoxProps { 5 | node: Node; 6 | onClick: () => void; 7 | isSelected: boolean; 8 | } 9 | 10 | const NodeBox: React.FC = ({ node, onClick, isSelected }) => { 11 | return ( 12 |
18 |

{node.title}

19 |

{node.summary}

20 |
21 | ); 22 | }; 23 | 24 | interface NodeListProps { 25 | nodes: Node[]; 26 | selectedNodeId: string; 27 | onNodeClick: (nodeId: string) => void; 28 | } 29 | 30 | const NodeList: React.FC = ({ 31 | nodes, 32 | onNodeClick, 33 | selectedNodeId, 34 | }) => { 35 | if (nodes.length === 0) return; 36 | // console.log(nodes); 37 | // const [selectedNodeId, setSelectedNodeId] = useState(nodes[0].id || ""); 38 | 39 | const handleNodeClick = (nodeId: string) => { 40 | // setSelectedNodeId(nodeId); 41 | onNodeClick(nodeId); 42 | }; 43 | 44 | return ( 45 |
46 | {nodes.map((node) => ( 47 | handleNodeClick(node.id)} 51 | isSelected={selectedNodeId === node.id} 52 | /> 53 | ))} 54 |
55 | ); 56 | }; 57 | 58 | interface NodeDetailsProps { 59 | url: string; 60 | } 61 | 62 | const NodeDetails: React.FC = ({ url }) => { 63 | if (!url) return; 64 | // console.log(url); 65 | return ( 66 |
67 |