Compare commits

...

462 Commits

Author SHA1 Message Date
github-actions[bot] b618e91e99 Release 0.2.10 (#704)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-10 16:43:18 +07:00
Thuc Pham 91ce4e1236 feat: support file server for python llamadeploy (#703)
* feat: support file server for python llamadeploy

* Create wise-ways-knock.md

* release chat-ui
2025-07-10 16:38:00 +07:00
github-actions[bot] 2b85420370 Release 0.2.9 (#699)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-01 14:58:46 +07:00
Thuc Pham 52cc37f206 feat: flag to enable useChatWorkflow (#697)
* feat: flag to enable useChatWorkflow

* add USE_CHAT_WORKFLOW config

* require CHAT_DEPLOYMENT and CHAT_WORKFLOW

* handleError

* onError

* revert error

* skip all api handling if in proxy mode

* revert adding option

* modify config

* not require workflow factory

* llamadeploy config

* fix proxy

* fix: serialize in config.js

* try modify next config

* fix: need basePath for other nextjs endpoints

* add condition

* use constants as central place to modify basePath

* add comment

* update constants

* check workflow factory

* Create brown-readers-whisper.md

* bump chat-ui

* fix lint
2025-07-01 14:38:30 +07:00
Thuc Pham 952b5b4908 fix: @jridgewell/sourcemap-codec issue (#700) 2025-06-30 17:17:49 +07:00
Andy Fowler e8004fd711 Fix broken devcontainer due to deleted repo (#698) 2025-06-30 10:56:27 +07:00
github-actions[bot] 48f6d849e6 Release 0.6.0 (#694)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-19 17:22:29 +07:00
Marcus Schiesser 02a9db3d40 chore: remove template and usellamaparse params 2025-06-19 16:19:16 +07:00
Marcus Schiesser 8fa8c3bad8 feat: readd asking for models for simple.ts (#693) 2025-06-19 09:58:47 +07:00
github-actions[bot] a221bc60f7 Release 0.1.23 (#690)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-13 17:51:48 +07:00
Thuc Pham 6c0fb51557 fix: stream not stop after sending HumanInputEvent (#689)
* fix: stream not stop after sending HumanInputEvent

* Create polite-bugs-develop.md

* decide to run e2e:ts:streaming or e2e:ts:server based on matrix.template-types

* fix scripts

* update changeset
2025-06-13 15:04:30 +07:00
github-actions[bot] 3589f946a9 Release 0.2.8 (#685)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-12 18:09:12 +07:00
Thuc Pham e2486eb080 feat: support human in the loop for TS (#686)
* feat: support human in the loop for TS

* add example for custom workflow

* fix: need to request humanResponseEvent to save missing step to snapshot

* refactor: human response data should be any

* refactor runWorkflow function to support resume stream

* refactor: hitl

* fix: workflow

* add summary event

* send tool event

* use requestId from Vercel

* update chat route.ts

* fix copy utils/*

* refactor: workflow and stream

* Create eight-moons-perform.md

* update typo

* make schema simple

* fix typo

* use messages in startAgentEvent

* save to snapshots folder

* fix lint

* feat: workflowBaseEvent

* include response event in input event

* simplify type

* update readme

* update document

* fix typecheck

* bump: "@llamaindex/workflow": "~1.1.8"

* remove any

* use fixed tsx version to fix e2e

* fix wrong copy

* add cli hitl examples as a use case for both Python and TS

* update changeset to release create-llama also

* fix e2e

* fix e2e

* hitl frontend chat

* try disable hitl test
2025-06-12 18:00:10 +07:00
Huu Le 66b81e5323 fix cannot catch the error raised from the workflow (#684) 2025-06-09 16:53:49 +07:00
github-actions[bot] 924649c025 Release 0.1.21 (#680)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-06 17:19:25 +07:00
Thuc Pham 1b04db917b fix lint for release (#682) 2025-06-06 16:43:45 +07:00
Thuc Pham af9ad3c42d feat: show document artifact after generating report (#658)
* feat: show document artifact after generating report

* keep chat message content as it is

* use artifactEvent from server

* add deep research example

* bump chat-ui for new editor

* import editor css

* hide warning for workflowEvent<{}>() in eject mode

* fix format

* use CL for better testing

* generate artifact after streaming report in Python

* bump chat-ui to support citations

* use isinstance to check stream

* fix document editor spacing

* Create tame-wolves-obey.md

* add sources to document artifact

* add sources to document artifact in python

* type cast

* no need score

* fix lint

* move handle stream logic to server

* refactor: use chunk.text and chunk.raw

* bump chat-ui 0.5.6 to fix citations

* update changset

* fix lock
2025-06-06 16:34:52 +07:00
Huu Le 1ff6eaf3e1 feat: Support upload private file (#674)
* init private support for python BE

* feat: Add private file handling and upload support in FastAPI

- Introduced `main.py` to set up the FastAPI application with file upload capabilities.
- Created `workflow.py` to manage file reading and tool creation for uploaded files.
- Updated `server.py` to include upload API configuration.
- Modified chat router to handle file uploads and return server file metadata.
- Refactored chat models to support new file handling structure.
- Enhanced file service to manage private file storage and retrieval.

* add process base64 and update examples

* add readme example

* fix test

* feat: Add file upload support to LlamaIndexServer TS

* add get_file to fileservice

* refactor: Simplify file storage logic in helpers.ts

* update example

* attach file to user message

* fix example, improve model

* feat: Add file upload support and enhance chat workflow in LlamaIndexServer

* remove redundant change

* support agent workflow for ts

* Enhance README and add file upload examples for LlamaIndex Server. Updated instructions for running examples and added new workflows for handling uploaded files. Included detailed notes on using file attachments in workflows.

* update doc

* update example

* Enhance README with detailed instructions for file upload in chat UI. Update custom workflow to handle file attachments and modify chat router to remove unused attachment handling. Refactor create_workflow to pass attachments from chat request.

* Refactor file handling in workflows by updating the create_file_tool function to accept file attachments directly. Introduce a new ServerFileResponse model for better file response handling. Update chat router to utilize the new FileUpload model for file uploads. Clean up imports and ensure consistent file attachment processing across workflows.

* Enhance file handling in workflows by updating README and example files. Introduce a new `workflowFactory` structure to support file attachments, and improve the `extractFileAttachments` function for better clarity and usability. Update descriptions in tools to reflect changes in file ID handling.

* fix unstoppable

* chore: fix issues

* add changeset

* bump chat-ui

* bump chat-ui for eject project

---------

Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-06-06 15:58:56 +07:00
Thuc Pham a543a27faf feat: bump chat-ui with inline artifact (#675)
* feat: bump chat-ui with inline artifact

* bump chat-ui 0.5.0

* update extractLastArtifact

* fix: imports

* fix: circle import

* missing export

* update document gen workflow

* remove artifactEvent for annotations

* update document

* bump chat-ui 0.5.1 to fix parsing $

* bump chat-ui 0.5.2

* toArtifactEvent internal

* update doc to use toArtifactEvent

* do workflow transformmation internal

* revert doc

* keep contract

* fix format

* update get_last_artifact to extract inline annotations in Python

* fix imports

* Transforms ArtifactEvent to AgentStream with inline annotation format

* Create thick-turtles-deny.md

* donot use relative imports

* toInlineAnnotationEvent

* to_inline_annotation_event in python

* refactor: move toInlineAnnotationEvent to inline.ts

* update comment

* rename ArtifactTransform to InlineAnnotationTransformer

* add codegen example

---------

Co-authored-by: leehuwuj <leehuwuj@gmail.com>
2025-06-05 10:20:21 +07:00
Thuc Pham 63edd74ba1 fix: conflict package versions in ts examples (#678) 2025-06-05 09:25:54 +07:00
Marcus Schiesser 13a967b2a2 docs: improved python readmes 2025-06-03 14:57:57 +07:00
Huu Le 2ac4d92493 chore: update examples (#677) 2025-06-03 14:33:27 +07:00
Marcus Schiesser 7e47cba4ba docs: clarify HITL example 2025-06-03 08:52:45 +07:00
github-actions[bot] bc56fa3c5f Release 0.5.20 (#671)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-02 18:02:05 +07:00
Huu Le 087c96164d feat: [server] Add Human in the Loop example with FastAPI integration (#630) 2025-06-02 17:47:04 +07:00
Thuc Pham 3ff0a18876 fix: default header padding (#672) 2025-05-31 14:08:29 +07:00
Thuc Pham df1047480a fix: missing cursor pointer for button (#670) 2025-05-30 09:52:17 +07:00
Marcus Schiesser 8d89223a08 chore: fill empty chat message default 2025-05-29 21:05:53 +07:00
github-actions[bot] 49a944182f Release 0.2.5 (#669)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-29 13:06:58 +07:00
Marcus Schiesser 058b3762c1 fix: update generate script path for ejected project (#668) 2025-05-29 12:21:17 +07:00
Thuc Pham 4c8579b04f use eject file in linux (#663) 2025-05-29 09:15:52 +07:00
github-actions[bot] bb1e82cdae Release 0.1.18 (#660)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-28 17:57:45 +07:00
Huu Le f682a1c36e chore: add project directory to Prettier ignore list (#659) 2025-05-28 17:50:23 +07:00
Huu Le b8a1ff6412 feat: Support citation for agentic template (#642) 2025-05-28 17:28:50 +07:00
Thuc Pham 5fe9e17d3f feat: support eject to fully customize next folder (#653) 2025-05-28 17:09:47 +07:00
Marcus Schiesser 15619d81a6 added claude code files 2025-05-27 13:39:57 +07:00
Huu Le 76742da78a chore: add python release condition (#656) 2025-05-27 09:25:36 +07:00
github-actions[bot] 693d7a0ea5 Release 0.5.18 (#655)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-26 18:43:41 +07:00
Huu Le 8d59ef0a6b chore: Add layout_dir config to the generated python code (#654) 2025-05-26 18:09:31 +07:00
github-actions[bot] c62f26e31c Release 0.1.17 (#652)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-26 11:21:23 +07:00
Huu Le d3f73679b4 chore: add server package path to ESLint ignore list (#651) 2025-05-26 10:58:40 +07:00
Huu Le 91c35cff33 fix release action didn't run custom version command (#650) 2025-05-26 10:43:11 +07:00
github-actions[bot] 82ac925224 Release 0.1.17 (#644)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-23 17:10:05 +07:00
thucpn f24ee8e6f9 fix: missing comma in config 2025-05-23 16:39:27 +07:00
Thuc Pham 3acec88fbc chore: bump chat-ui (#645) 2025-05-23 15:18:17 +07:00
Thuc Pham eee3230e99 feat: support custom layout (#641) 2025-05-23 14:18:22 +07:00
Marcus Schiesser d8425e5290 docs: fix type 2025-05-23 13:22:11 +07:00
Huu Le 0bc5a0d882 feat: Add config for suggest next question (#640)
* Enhance LlamaIndexServer with next question suggestion feature

- Added `suggest_next_questions` parameter to the LlamaIndexServer for suggesting follow-up questions after the assistant's response.
- Updated README.md to document the new configuration option.
- Introduced `SUGGEST_NEXT_QUESTION_PROMPT` in prompts.py for customizable question suggestions.
- Bumped version to 0.1.16 in uv.lock to reflect the new feature.

* Implement next question suggestion feature in LlamaIndexServer

- Added `suggestNextQuestions` option to LlamaIndexServer for suggesting follow-up questions after the assistant's response.
- Updated README.md to include the new configuration option.
- Modified example workflow to utilize the new feature.
- Enhanced chat handler to conditionally send suggested questions based on the new option.

* add changeset

* remove log

* bundle ui instead of download

* check test

* check test

check test

check test

check test

check test

check test

check test

check test

check test

check test

* fix tests

* Update artifact path in workflow and clarify README.md text

- Changed the artifact path in the GitHub Actions workflow from `python/llama-index-server/dist/` to `dist/`.
- Revised README.md to clarify the default prompt used for the `suggest_next_questions` configuration option.

* support changeset for python

* refactor: update llama-index-server structure and workflows

* fix workflows

* fix workflows

* fix workflows

* add changeset

* fix cannot release python

* Update packages/server/README.md

Co-authored-by: Thuc Pham <51660321+thucpn@users.noreply.github.com>

* Update starter questions in LlamaIndex App and add TODO for suggestion feature in chat API

---------

Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
Co-authored-by: Thuc Pham <51660321+thucpn@users.noreply.github.com>
2025-05-23 12:48:45 +07:00
github-actions[bot] bbae802bed Release 0.2.2 (#638)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-22 17:17:34 +07:00
Thuc Pham 25fba4381b refactor: migrate to Nextjs Route Handler (#625) 2025-05-22 11:47:24 +07:00
Huu Le d0618fa2fa add changeset (#639) 2025-05-21 14:31:41 +07:00
Huu Le f3fe3ffc9b fix: llamacloud generate not working and re-add tests (#636) 2025-05-21 12:49:44 +07:00
Thuc Pham 6f75d4ab6e fix: unsupported language in code gen workflow (#633) 2025-05-21 12:31:11 +07:00
Huu Le 3242738fe4 chore: Fix Python e2e tests (#632) 2025-05-21 11:30:02 +07:00
Sourabh Kondapaka 17538eb0dd Fixed bug when traceloop observability is chosen but does not install the latest version (#603)
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-05-20 11:48:32 +07:00
github-actions[bot] d3772cb4a2 Release 0.5.15 (#629)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-16 16:33:35 +07:00
Huu Le 527075c086 enable dev mode that allows updating code directly in the UI (#624)
* Enable dev mode that allows updating code directly in the UI

* bump server packages
2025-05-16 16:05:56 +07:00
github-actions[bot] fb7d4da149 chore(release): bump llama-index-server version to 0.1.16 (#587)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-16 15:16:57 +07:00
leehuwuj 5c35b194bb bump chat ui version 2025-05-16 14:53:57 +07:00
github-actions[bot] 85e5e7e662 Release 0.5.14 (#608)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-16 14:41:46 +07:00
Huu Le 58362542c0 chore: add workflow contract for server (#623) 2025-05-16 14:26:24 +07:00
Thuc Pham 6f44185f68 fix: init messages memory in start event handler (#627) 2025-05-16 12:45:35 +07:00
Thuc Pham afe9e9fc16 fix: nodemon should ignore temp file (#622) 2025-05-15 15:33:24 +07:00
Thuc Pham 1b5a519f13 chore: improve dev experience with nodemon (#621) 2025-05-15 15:18:12 +07:00
Huu Le f072308d03 feat: Add dev mode (#610)
* Add UI components and static assets for chat interface

* feat: Add simple chat app example with FastAPI integration

* fix: update default workflow file path and improve error handling

* update doc

* change to file_path

* include changes from #614

* fix mypy

* support devmode for backend ts server

* Revert "support devmode for backend ts server"

This reverts commit bd943fd8c1.

* fix: polling should work when server not yet started

* bump chat-ui to fix syntax highlight issue

* fix: missing language for code editor

* enhance UI with shadow overlay

* enhance doc

* fix minor UI bugs

* enhance doc

* remove unessesary debug log

* fix wrong check

* increase delay time before trigger polling

* feat: support dev mode for backend ts server (#616)

* feat: support dev mode for backend ts server

* update message

* validate typescript file

* fix: format

* use temp file to avoid server restart

* fix format

* use npx tsc

* remove typescript deps

---------

Co-authored-by: thucpn <thucsh2@gmail.com>
Co-authored-by: Thuc Pham <51660321+thucpn@users.noreply.github.com>
2025-05-15 14:56:08 +07:00
Huu Le 1df8cfbdc2 refactor: split artifacts use case into document generator and code generator (#617)
* split artifacts use case to code generator and document generator

* add changeset

* fix package version

* fix typing

* bump openai

* fix package

* fix typing

* fix: improve type handling and clean up UI event component

- Removed unnecessary string conversion for userInput in code_generator and deep_research workflows.
- Updated userRequest type to MessageContent for better type safety.
- Cleaned up the UI event component by removing redundant indicatorClassName logic.

* docs: word smith

* better handler typing

* refactor: remove redundant UI event handling in workflows

---------

Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-05-15 14:22:21 +07:00
Thuc Pham 24515393a6 fix: remove dead generated ai code (#618) 2025-05-14 12:42:43 +07:00
Huu Le b3eb0ba7d4 fix typing issue and add typing test for llamaindexserver templates (#613)
* try testing for llamaindexserver

* Enhance TypeScript tests for dependency resolution by introducing template types and use cases

* refactor template structure

* fix package conflict

* add tests for python

* fix python mypy

* use matrix for templateType

* add changeset

* add removing data.ts for artifacts template

* don't ask llamacloud for unsupported use case and skip test

* Enhance tests for LlamaIndexServer by adding conditional skips based on data source and refining use case tests for example data source
2025-05-13 16:20:24 +07:00
Huu Le 556f33c0ab pin onnxruntime version to fix issue on Windows (#609) 2025-05-12 16:25:47 +07:00
Marcus Schiesser 7a70390b00 chore: deprecate pro mode (#607) 2025-05-12 12:07:55 +07:00
github-actions[bot] ad5912b41f Release 0.5.13 (#605)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-09 17:31:53 +07:00
Marcus Schiesser 76502d28e7 chore: remove changeset 2025-05-09 17:29:50 +07:00
Huu Le f4ca602da5 feat: Add artifact use case and use new the workflow for Typescript (#595)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-05-09 17:20:30 +07:00
Thuc Pham d304554f33 feat: add examples package for easily testing workflow (#599) 2025-05-08 17:15:00 +07:00
github-actions[bot] 8dce9f913d Release 0.2.0 (#591)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-08 17:14:11 +07:00
Marcus Schiesser c62096c516 fix: server packages (#598) 2025-05-08 16:34:51 +07:00
Huu Le 0384268543 Support the new workflow for @llamaindex/server (#592)
* use the new llama-flow workflow

* update create-llama

* update deep research workflow to use llama-flow

* update ts config to avoid overhead checking python

* Refactor workflows to use startAgentEvent and remove workflowInputEvent. Clean up unused imports and improve error handling for missing user input.

* Refactor runWorkflow to utilize AgentInputData and improve error handling for missing user input. Replace workflowInputEvent with startAgentEvent and enhance chat history management. Add callbacks for suggested questions event.

* Implement code artifact workflow and update TypeScript helpers. Introduce new `code_workflow.ts` for managing code generation and updates, and create a factory function in `workflow.ts`. Modify TypeScript helper to copy all `.ts` files instead of just `workflow.ts`. Update chat handler to utilize `AgentInputData` for improved data handling.

* Refactor runWorkflow to utilize the run function for workflow execution, replacing the previous context creation method. This change simplifies the workflow stream initialization and enhances code clarity.

* Refactor workflows to replace stopEvent with stopAgentEvent and enhance event handling in code_workflow.ts and workflow.ts. Update grammar in enhancedPrompt for clarity and improve response handling in agentStreamEvent.

* Refactor financial report workflow to streamline event handling and improve memory management. Replace custom event classes with workflowEvent for better clarity and maintainability. Update workflow definition to utilize getWorkflow function, enhancing code organization and readability.

* Add document and code artifact workflows with event handling improvements

- Introduced `doc-workflow.ts` for managing document generation and updates.
- Created `code-workflow.ts` for code artifact management.
- Enhanced event handling with `workflowEvent` for better clarity and maintainability.
- Updated `README-template.md` to include setup instructions and use cases for new workflows.
- Modified `workflow.ts` to allow switching between code and document workflows.
- Improved grammar and clarity in prompts and comments throughout the code.

* Refactor workflow.ts to replace ReadableStream with TransformStream for improved event handling. Introduce workflowToEngineResponseStream function to streamline the processing of workflow events and enhance error handling. Update return statement in runWorkflow to utilize the new stream implementation.

* add changesets

* Remove redundant totalQuestions update in getWorkflow function to streamline event processing.

* Migrate workflow types to @llamaindex/workflow package and update imports

* Replace @llama-flow/core with @llamaindex/workflow and update stream handling

* update workflows

* update import for agentic rag

* fix wrong import

* init new stream method

* Refactor stream handling in workflow.ts and stream.ts to utilize WorkflowStream type. Update processWorkflowStream function for improved event processing and clarity. Enhance imports from @llamaindex/workflow.

* Refactor stream handling in request.ts and stream.ts to improve type usage and error handling. Update toDataStreamResponse function to toDataStream and enhance callback functionality for better stream management in workflow.ts.

* Refactor server.ts, types.ts, and chat.ts to streamline workflow type usage and improve error handling. Update toDataStream function in stream.ts for better data streaming and processing. Enhance imports from @llamaindex/workflow for consistency.

* Enhance chat handler to include suggested questions functionality. Refactor toDataStream in stream.ts to support callback options for onStart, onText, and onFinal events. Export generateNextQuestions function in suggestion.ts for improved accessibility.

* Refactor workflow imports in deep_research and financial_report templates to enhance consistency and organization. Update package.json to include @llamaindex/workflow version 1.1.0. Remove commented-out code in gen-ui.ts for cleaner implementation.

* remove log

* fix incorrect toolcall llm check

* relock

* revert changes on create-llama
2025-05-07 17:15:07 +07:00
Thuc Pham d9f9e3c1c3 chore: bump chat-ui to support code editor & document editor (#594) 2025-05-06 16:56:26 +07:00
Thuc Pham 1357c423a3 chore: move lint & prettier configs to root (#590)
* chore: move lint & prettier configs to root

* update prettier config

* fix: format

* use bunchee in root

* move typescript packages to root

* apply recommened typescript rules for create-llama and fix lints

* apply prettier-plugin-tailwindcss to auto sort tailwind classnames

* Create ninety-goats-draw.md
2025-04-29 16:45:57 +07:00
github-actions[bot] 8105aa70b6 Release 0.5.12 (#589)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-29 15:48:08 +07:00
Marcus Schiesser 23a90625d1 chore: add ruff check 2025-04-29 15:47:13 +07:00
Marcus Schiesser ac789bcb8d chore: check python format 2025-04-29 15:42:10 +07:00
Huu Le 241d82a87d feat: add create-llama artifacts template (python) (#586)
* add artifact template for python

* Add artifact workflows for code and document generation

- Introduced `CodeArtifactWorkflow` and `DocumentArtifactWorkflow` classes to handle code and document artifacts respectively.
- Updated README to include instructions for modifying the factory method to select the appropriate workflow.
- Enhanced clarity in class documentation and improved naming conventions for better understanding.

* bump packages

* fix wrong name

* add ts workflows

* revert change for TS

* docs: fix docs

* add metadata fields

---------

Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-04-29 14:22:16 +07:00
github-actions[bot] b16cfd873b chore(release): bump llama-index-server version to 0.1.15 (#576)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-28 15:55:05 +07:00
Huu Le 3130cdf18d Add support for artifact in llama-index-server (#580)
* support artifact

* migrate poetry to uv

* fix ci

* update ci

* Refactor artifact generation tools by introducing separate CodeGenerator and DocumentGenerator classes. Update app_writer to utilize FunctionAgent for code and document generation workflows. Remove deprecated ArtifactGenerator class. Enhance artifact transformation logic in callbacks. Improve system prompts for clarity and instruction adherence.

* enhance code

* remove previous content from tool input

* fix test

* bump chat ui

* revert changes

* remove dead code

* Add artifact workflows for code and document generation

- Introduced `code_workflow.py` for generating and updating code artifacts based on user requests.
- Introduced `document_workflow.py` for generating and updating document artifacts (Markdown/HTML).
- Created `main.py` to set up FastAPI server with artifact workflows.
- Added a README for setup instructions and usage.
- Implemented UI components for displaying artifact status and progress.
- Updated chat router to remove unused event callbacks.

* remove app_writer workflow

* Refactor artifact workflow classes and UI event handling

- Renamed `ArtifactUIEvents` to `UIEventData` for clarity.
- Introduced `last_artifact` attribute in `ArtifactWorkflow` to streamline artifact retrieval.
- Updated chat history handling to utilize the new `last_artifact` attribute.
- Modified event streaming to use `UIEventData` for consistent event structure.
- Added a new UI component for displaying artifact workflow status and progress.

* Use uv to release package

* Refactor artifact workflows and UI components

- Updated `code_workflow.py` and `document_workflow.py` to improve chat history handling and user message storage.
- Enhanced `ArtifactWorkflow` to utilize optional fields in the `Requirement` model.
- Revised prompt instructions for clarity and conciseness in generating requirements.
- Modified UI event components to reflect changes in workflow stages and improve user feedback.
- Improved error handling for JSON parsing in artifact annotations.

* move code

* Merge remote-tracking branch 'origin/main' into lee/add-artifact

* sort artifact

* fix mypy

* fix adding custom route does not work

* fix mypy

* revert create-llama change

* disable e2e test for python package change

* fix missing set memory

* remove include last artifact in the code

* Add ArtifactEvent model and update workflows to use it
2025-04-28 15:49:20 +07:00
github-actions[bot] 7711216134 Release 0.5.11 (#582)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-28 14:48:28 +07:00
Marcus Schiesser 93d601972e docs: fix llamaindexserver 2025-04-28 14:46:55 +07:00
Thuc Pham 8fe5fc24c1 chore: add llamaindex server package (#585) 2025-04-28 14:37:12 +07:00
Thuc Pham 3960618454 chore: create-llama monorepo (#581)
* chore: create-llama monorepo

* add root package.json and pnpm workspace

* keep e2e inside create-llama

* update root package.json

* move scripts and dev dependencies of create-llama to root

* update e2e test for create-llama package

* update lint workflow

* update release llama-index-server workflow

* update path for test_llama_index_server workflow

* remove local lock file

* keep lint and format in create-llama

* fix: format

* update pre-commit

* move playwright back to create-llama

* disable pnpm for installing generated frontend

* use npm for type check

* update gitignore

* try --ignore-workspace option

* Move llama-index-server from packages/python-server to python directory

* update CI for python server

* Create plenty-spies-tickle.md
2025-04-25 18:38:02 +07:00
github-actions[bot] 53e1cd56e7 Release 0.5.10 (#579)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-22 15:45:31 +07:00
Huu Le 0a2e12a2bb Use uv as the default package manager and deprecate poetry. (#578) 2025-04-22 15:44:11 +07:00
github-actions[bot] 2e536dca36 Release 0.5.9 (#577)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-18 19:35:10 +07:00
Huu Le 4bc53ac24e feat: support UI generator for TS (#566) 2025-04-18 19:14:29 +07:00
github-actions[bot] 2deb63a6cc chore(release): bump version to 0.1.14 (#567)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-18 17:54:50 +07:00
github-actions[bot] 2ffa057f77 Release 0.5.8 (#573)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-18 17:51:19 +07:00
Huu Le 64f151dd66 bump chat ui (#575) 2025-04-18 17:43:22 +07:00
Thuc Pham 765181adb0 chore: test typescript e2e with node 20 and 22 (#572)
* chore: test typescript e2e with node 20 and 22

* Create sixty-chefs-search.md
2025-04-17 10:06:35 +02:00
github-actions[bot] 95c35e8a5c Release 0.5.7 (#571)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-17 13:51:52 +07:00
Thuc Pham 598865768a chore: bump llmaindex (#570) 2025-04-17 13:49:53 +07:00
github-actions[bot] 05453d55bf Release 0.5.6 (#569)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-16 20:40:15 +07:00
Huu Le d363ced4d8 bump llamaindex server package versions to 0.1.13 (python) and 0.1.3 (ts) (#568) 2025-04-16 20:38:58 +07:00
github-actions[bot] 293c6f97c1 chore(release): bump version to 0.1.13 (#561)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-16 16:29:41 +07:00
Huu Le 44b4d89ac1 Update document link and fix import (#565) 2025-04-16 16:23:17 +07:00
github-actions[bot] 60f10c5b5d Release 0.5.5 (#564)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-15 20:55:53 +07:00
Huu Le ee85320701 fix: missing default export (#563) 2025-04-15 20:54:23 +07:00
github-actions[bot] b12dc6f1e8 Release 0.5.4 (#562)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-15 18:28:11 +07:00
Huu Le 7c3b279417 support code generation of event components using an LLM (Python) (#557)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-04-15 18:23:06 +07:00
github-actions[bot] 1514a555d5 chore(release): bump version to 0.1.12 (#559)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-15 17:32:13 +07:00
Huu Le cddb4f6bcc chore: bump chat UI version to 0.1.2 and rename generate_ui_for_workflow (#560)
* chore: bump chat UI version to 0.1.2 and rename generate_ui_for_workflow

* feat: add exports for event component generation in gen_ui module

* update document

* refine prompt
2025-04-15 17:27:22 +07:00
github-actions[bot] c82e4f5791 chore(release): bump version to 0.1.11 (#555)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-15 13:11:15 +07:00
Huu Le 1f7e0e3c69 add GenUIWorkflow for generating UI components from workflow events (#549)
* feat: add GenUIWorkflow for generating UI components from workflow events

* feat: enhance GenUIWorkflow to support event handling and UI generation

* add cache, split code

* use gemini model

* refactor: update GenUIWorkflow to use Anthropic model and add pre-run checks for API key and package installation

* feat: introduce PlanningEvent and enhance GenUIWorkflow for improved UI planning and aggregation function generation

* feat: add gen ui to llamaindexserver

* refactor: remove unused gen_ui.py file

* simplify

* update for tailwindcss

* simplify code and add document

* refine text

* feat: add UIEvent model and update exports in server module

* use default UIEvent

* fix wrong model, update template

* add missing doc

* fix linting

* revert change on template

* fix mypy

* disable e2e for the change from llama-index-server

* remove unused script entry from pyproject.toml and refine UI notice text in GenUIWorkflow

* update workflow, bump chat ui

* Refine GenUIWorkflow documentation and improve code structure notes; add llm parameter to generate_ui_for_workflow function.
2025-04-15 13:06:55 +07:00
github-actions[bot] 7997cdeb70 Release 0.5.3 (#556)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-10 19:08:02 +07:00
Huu Le 76ec3605e5 update templates to use new chat UI config (#553) 2025-04-10 19:03:06 +07:00
github-actions[bot] 5cfdec7d75 chore(release): bump version to 0.1.10 (#550)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-10 17:47:23 +07:00
Huu Le 3d1b15d515 fix encoding windows (#554) 2025-04-10 17:37:49 +07:00
Huu Le 392393af9e feat: Add config app title for python, enhance config parameter. (#540)
* Enhance LlamaIndexServer UI configuration

* bump version, add use llamacloud to chat ui config

* add changeset

* refactor: streamline UI configuration and component directory handling

* relock and fix test

* remove change set

* update docs

* fix wrong key name

* fix test

* bump chat ui

* improve docs
2025-04-10 16:45:20 +07:00
Marcus Schiesser 920beda8ad chore: use own DeepResearchEvent (#552) 2025-04-09 20:44:38 +07:00
github-actions[bot] e6f8add778 Release 0.5.2 (#551)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-09 19:40:36 +07:00
Huu Le c9f8f8d5f2 feat: Use custom component for deep research use case (#548) 2025-04-09 19:31:09 +07:00
github-actions[bot] 24eb7736ee chore(release): bump version to 0.1.9 (#545)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-09 19:01:03 +07:00
Huu Le 5fb27220f7 feat: Add componentDir for llama_index_sever (#547)
* init code for custom components

* change router name

* use jsx

* add custom components code

* revert change on create-llama

* fix mypy

* adding document for custom component

* Refactor component directory handling in LlamaIndexServer

* add file name in components response

* Enhance documentation

* fix mypy

* use tmp in test

* docs: word smithing

* Refactor component loading logic in CustomUI to prioritize TSX over JSX files and improve duplicate handling.

* bump chat ui

---------

Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-04-09 18:51:39 +07:00
github-actions[bot] 5caa3813f8 chore(release): bump version to 0.1.8 (#534)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-03 21:33:54 +07:00
github-actions[bot] bc95789a8d Release 0.5.1 (#544)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-03 15:25:09 +02:00
Huu Le 08b3e079e4 chore: simplify local index code (#537) 2025-04-03 14:21:50 +02:00
Huu Le 1876950f89 fix null embedding model name when create llamacloud index (#543) 2025-04-03 13:10:19 +02:00
ForgQi c7349b44c4 fix: bump llama-index-core to fix handle missing fields parameter in default_formatter (#542)
* fix: handle missing fields parameter in default_formatter to avoid runtime error

https://github.com/run-llama/llama_index/pull/18340

* relock packages

---------

Co-authored-by: leehuwuj <leehuwuj@gmail.com>
2025-04-03 16:35:51 +07:00
github-actions[bot] 4068618b2d Release 0.5.0 (#508)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-02 19:34:57 +07:00
Huu Le 54c9e2f95e Feature: Simplify app code using LlamaIndexServer (#529)
---------
Co-authored-by: thucpn <thucsh2@gmail.com>
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-04-02 19:31:06 +07:00
github-actions[bot] aec1173b71 chore(release): bump version to 0.1.7 (#531)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-02 17:50:01 +07:00
Huu Le 481663dd63 chore(release): bump CHAT_UI_VERSION to 0.0.6 (#533) 2025-04-02 17:35:58 +07:00
Huu Le 1ca7dd2e48 fix llamacloud api and markdown issue (#532) 2025-04-02 17:07:25 +07:00
github-actions[bot] 3d20990713 chore(release): bump version to 0.1.6 (#528)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-01 22:25:51 +07:00
Huu Le 8fb69cf807 feat: add llamacloud to llama_index_server (#530) 2025-04-01 22:23:34 +07:00
github-actions[bot] 61af56dac6 chore(release): bump version to 0.1.5 (#526)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-26 22:41:43 +07:00
Huu Le 4b66039a96 update variable (#527) 2025-03-26 22:40:34 +07:00
github-actions[bot] ee88f681a6 chore(release): bump version to 0.1.4 (#524)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-26 17:55:02 +07:00
Huu Le 992c3a95e9 update release workflow for llama-index-server (#525) 2025-03-26 17:53:33 +07:00
github-actions[bot] 2a4fb702d1 chore(release): bump version to 0.1.3 (#522)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-26 17:39:28 +07:00
Huu Le 24b9337096 fix: poetry release ci (#523)
* Fix unnecessary create PR and wrong PyPI environment name

* use JRubics/poetry-publish
2025-03-26 17:36:53 +07:00
github-actions[bot] fceec69a3a chore(release): release llama-index-server 0.1.2 (#520)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-26 17:10:54 +07:00
Huu Le 03e5e0a16e fix release ci, add --no-interaction (#521) 2025-03-26 17:09:16 +07:00
github-actions[bot] fe3cd36d3a chore(release): bump version to 0.1.1 (#517)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-26 16:55:29 +07:00
Huu Le d5d10e9ead Support overriding UI configuration for LlamaIndexServer (#519)
* support for ui config override

* remove dead code

* bump chat ui

* fix linting
2025-03-26 16:39:27 +07:00
Huu Le 5ed925d75f stream ToolCallResult event in agent tool utils (#518) 2025-03-26 13:38:50 +07:00
Huu Le ca5df14d41 feat: Add llama_index_sever (#516) 2025-03-25 20:59:52 +07:00
Thuc Pham ee69ce7cc1 bump: chat-ui and tailwind v4 (#509) 2025-02-25 09:38:31 +07:00
Thuc Pham 0e4ecfaf8b fix: add trycatch for generating error (#507) 2025-02-20 16:34:14 +07:00
github-actions[bot] 3658fec684 Release 0.4.0 (#499)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-20 11:11:09 +07:00
Marcus Schiesser c3d275abe1 make minor release 2025-02-20 11:07:56 +07:00
Thuc Pham 61204a1381 chore: bump LITS 0.9 (#505)
---------
Co-authored-by: leehuwuj <leehuwuj@gmail.com>
2025-02-20 10:33:22 +07:00
Huu Le 9e723c3a15 Standardize the code of workflow use cases (#495) 2025-02-05 11:10:47 +07:00
Thuc Pham d5da55b993 feat: add components.json to use CLI (#501) 2025-02-05 11:04:16 +07:00
Thuc Pham c1552ebb00 chore: move wikipedia tool to create-llama (#498) 2025-02-03 17:35:19 +07:00
github-actions[bot] 131e63ae4a Release 0.3.28 (#494)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-22 17:37:12 +07:00
Huu Le 4e06714cdd Fix: deep research use case (#493) 2025-01-22 17:24:12 +07:00
Ravi Kumar 18c8d2540c added EMBEDDING_DIM if available or undefined to fallback to default config (#487) 2025-01-22 12:00:26 +07:00
github-actions[bot] d4b4338f54 Release 0.3.27 (#492)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-22 10:59:19 +07:00
Huu Le b4e41aa526 feat: Add deep research use case (Python) (#482)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-01-22 10:22:49 +07:00
github-actions[bot] 860b9d46d4 Release 0.3.26 (#484)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-17 17:14:45 +07:00
Huu Le f73d46bf10 fix missing multiagent code (#483) 2025-01-17 16:59:05 +07:00
github-actions[bot] eec237c5fe Release 0.3.25 (#477)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-27 13:11:44 +07:00
Thuc Pham 5450096e96 bump: react 19 stable (#476) 2024-12-27 13:01:59 +07:00
github-actions[bot] 163492f189 Release 0.3.24 (#472)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-27 09:54:29 +07:00
Huu Le a84743c576 add LlamaCloud support for reflex template (#473) 2024-12-26 15:09:16 +07:00
Thuc Pham fc5e56efa5 bump: code interpreter v1 (#469) 2024-12-26 15:06:00 +07:00
Huu Le a7a6592441 Fix the npm issue when running a fullstack Python app (#471) 2024-12-25 10:28:50 +07:00
github-actions[bot] af21426952 Release 0.3.23 (#470)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-24 16:40:23 +07:00
Huu Le 9077cae2f5 feat: Add legal document review use case (#467) 2024-12-24 15:38:37 +07:00
github-actions[bot] 765d2c4fff Release 0.3.22 (#463)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-12 10:57:51 +07:00
Marcus Schiesser 25667d45e9 feat: Make OpenAPI spec usable by custom GPTs (#462) 2024-12-11 17:10:23 +07:00
Sergey Lyapustin d31910a303 Fixed NEXT_QUESTION_PROMPT to suggest user questions. (#461) 2024-12-09 10:46:06 +07:00
github-actions[bot] 9852e7399c Release 0.3.21 (#459)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-06 16:41:24 +07:00
Marcus Schiesser 95227a7539 feat: add simple query endpoint (#458) 2024-12-06 16:12:52 +07:00
github-actions[bot] 71f29ea85d Release 0.3.20 (#457)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-06 12:15:32 +07:00
Huu Le 27d2499aff Bump llamacloud index and fix issues (#456) 2024-12-03 17:03:30 +07:00
github-actions[bot] a07f320e6d Release 0.3.19 (#455)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-02 11:39:29 +07:00
Huu Le f9a057ddde feat: add support for multimodal indexes (#453)
---------
Co-authored-by: thucpn <thucsh2@gmail.com>
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-11-29 18:02:14 +07:00
Thuc Pham aedd73d8c0 bump: chat-ui (#454) 2024-11-29 11:57:48 +07:00
github-actions[bot] da4505aff7 Release 0.3.18 (#451)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-27 16:56:27 +07:00
Huu Le 63e961e635 Refactor query engine tool code and use auto_routed mode for LlamaCloudIndex (#450)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-11-27 16:35:50 +07:00
Thuc Pham fe90a7e7ee chore: bump ai v4 (#449) 2024-11-27 12:26:53 +07:00
Huu Le 02b2473103 feat: Improve FastAPI agentic template (#447)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-11-26 10:54:22 +07:00
github-actions[bot] f17449b90a Release 0.3.17 (#446)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-22 16:36:36 +07:00
Huu Le 28c8808ce3 feat: Add fly.io deployment (#443)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-11-22 16:34:37 +07:00
Marcus Schiesser 0a7dfcf84b feat: Generate NEXT_PUBLIC_CHAT_API for NextJS backend to specify alternative backend (#445) 2024-11-22 11:06:38 +07:00
github-actions[bot] 6e70e327d3 Release 0.3.16 (#440)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-21 11:41:02 +07:00
Huu Le 8b371d8347 chore: fix incompatible with pydantic (#442) 2024-11-21 11:38:52 +07:00
Huu Le 30fe269575 Update duckduckgo tool option (#439) 2024-11-20 17:26:42 +07:00
Marcus Schiesser 49c35b834b docs: improve python readme 2024-11-20 13:30:08 +07:00
github-actions[bot] 82c2580ee5 Release 0.3.15 (#438)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-20 12:47:24 +07:00
Huu Le fc5b266a40 Simplify FastAPI fullstack template by using one deployment (#423)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-11-20 12:38:06 +07:00
Huu Le f8f97d2c00 Add support for Python 3.13 (#436) 2024-11-20 09:58:39 +07:00
github-actions[bot] 9c2e094883 Release 0.3.14 (#425)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-19 13:36:00 +07:00
Thuc Pham 00f0b3ae03 fix: dont include new message in chat history (#432) 2024-11-18 19:07:54 +07:00
Thuc Pham 4663dec81d chore: bump react19 rc (#430) 2024-11-18 16:47:51 +07:00
Huu Le 7f14e47f56 feat: Improve CI (#431) 2024-11-18 16:41:45 +07:00
Thuc Pham 6925676013 feat: use latest chat UI (#418)
---------

Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-11-14 11:48:10 +08:00
Thuc Pham 44b34fb464 chore: update nextjs v15, react v19 and eslint v9 (#420) 2024-11-14 09:47:35 +07:00
github-actions[bot] a108911fc1 Release 0.3.13 (#424)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-13 20:36:32 +08:00
Huu Le 282eaa07fc Fix: ts upload file does not create index and document store (#422) 2024-11-13 19:47:28 +08:00
Marcus Schiesser 80db5f7c46 add help comment 2024-11-13 14:50:23 +08:00
github-actions[bot] 7a22c9f56d Release 0.3.12 (#416)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-13 13:28:23 +07:00
Huu Le 8431b788ad feat: Add form filling use case for TS and optimize workflows (#417) 2024-11-13 12:45:57 +07:00
Marcus Schiesser 2b712cebec chore: remove dead code 2024-11-07 10:13:47 +08:00
Huu Le 6edea6af5c enhance workflow code for Python (#412)
* enhance workflow shared code

* fix streaming

* refactor code

* add missing helper

* update

* update form filling

* add filters

* simplify the code

* simplify the code

* simplify the code

* update form filling

* update e2e

* update function calling agent

* fix unneeded condition

* Create light-parrots-work.md

* revert change on using functioncallingagent

* update readme

* clean code

* extract call one tool function

* update for blog use case

* fix streaming

* fix e2e

* fix missing await

* improve tools code

* improve assertion code

* skip form filling test for TS framework

* update for tools helper

---------

Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-11-06 14:38:12 +07:00
Tom Aarsen d79d1652d1 Add new example HF embedding models (#415)
from https://huggingface.co/models?library=sentence-transformers
2024-11-05 16:12:07 +07:00
github-actions[bot] 8ebd8d7039 Release 0.3.11 (#409)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-04 16:41:34 +07:00
Marcus Schiesser 2b8aaa835d Add support for local models via Hugging Face (#414) 2024-11-04 16:39:27 +07:00
Huu Le 1fe21f85bd chore: Fix highlight.js issue with Next.js static build (#413) 2024-11-04 14:25:26 +07:00
Marcus Schiesser b9570b2eb9 fix: use generic LLMAgent instead of OpenAIAgent (adds support for Gemini and Anthropic for Agentic RAG) (#410) 2024-11-04 11:34:13 +07:00
Thuc Pham 00009ae53e feat: import pdf css (#408) 2024-11-01 17:21:08 +07:00
github-actions[bot] 63558c11fa Release 0.3.10 (#407)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-01 16:07:15 +07:00
Thuc Pham 9172fed2e8 feat: bump LITS 0.8.2 (#406)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-11-01 15:06:31 +07:00
Thuc Pham 78ccde78fc feat: integrate llamaindex chat-ui (#399)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-11-01 12:19:29 +07:00
github-actions[bot] 02510703d8 Release 0.3.9 (#405)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-31 16:05:33 +07:00
Huu Le ed59927bd0 feat: Add form filling use case for Python (#403)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-10-31 16:01:53 +07:00
Thuc Pham 9f866aa981 fix: use uploaded filename to build file url (#404) 2024-10-30 14:47:11 +07:00
github-actions[bot] b8f78612b8 Release 0.3.8 (#396)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-25 16:47:26 +07:00
Huu Le 4a8346900d feat: Add multi-agent financial report use case for TS (#394) 2024-10-25 16:44:56 +07:00
github-actions[bot] 42e63842d0 Release 0.3.7 (#395)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-25 14:34:55 +07:00
Huu Le fa803787e3 relative url (#393) 2024-10-25 14:13:34 +07:00
github-actions[bot] c5559d8e59 Release 0.3.6 (#392)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-23 17:51:46 +07:00
Huu Le 0182368744 Fix: UI streaming issue (#391) 2024-10-23 17:38:48 +07:00
github-actions[bot] ff46bd6153 Release 0.3.5 (#390)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-23 16:40:11 +07:00
Huu Le 2209409cdb Feature: Update multi-agent template to use financial report use case (#386)
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-10-23 16:36:12 +07:00
github-actions[bot] 623f8b811b Release 0.3.4 (#389)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-22 17:25:00 +07:00
Huu Le 384a1368dd Add mypy checker for importing and update CI condition (#387) 2024-10-22 17:00:52 +07:00
github-actions[bot] 189c0e3f6c Release 0.3.3 (#383)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-22 10:50:58 +07:00
Huu Le 99b8247bc9 Enhance data type (#378) 2024-10-17 16:37:14 +07:00
github-actions[bot] 74c5a15450 Release 0.3.2 (#381)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-17 11:39:38 +07:00
Marcus Schiesser 9293e330ac Update demo video in README.md 2024-10-17 11:38:22 +07:00
Marcus Schiesser 6d1b6b9372 docs: readme update for pro mode 2024-10-17 11:13:00 +07:00
github-actions[bot] a8162a9269 Release 0.3.1 (#377)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-16 15:23:09 +07:00
Huu Le f3577c50d6 add data scientist use case (directly using uploaded files) (#355)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
Co-authored-by: Thuc Pham <51660321+thucpn@users.noreply.github.com>
2024-10-16 14:00:59 +07:00
Huu Le a5f5c9dc9c fix always ask post installation action (#376) 2024-10-16 09:52:25 +07:00
Huu Le 2be68d1c7f ci: activate llamacloud for TS (#372)
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-10-15 13:40:47 +07:00
Thuc Pham 8c80cc05ce fix: enhance performance for codeblock (#347)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-10-15 12:21:08 +07:00
github-actions[bot] dfd4fd58ab Release 0.3.0 (#368)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-14 16:25:37 +07:00
Thuc Pham 0a69fe09fa fix: missing params when init Astra vectorstore (#373) 2024-10-14 16:03:41 +07:00
Marcus Schiesser de88b32208 fix: remove llamacloud for extractor 2024-10-14 15:35:59 +07:00
Marcus Schiesser ef88bff211 chore: upgrade reflex 2024-10-14 15:09:16 +07:00
Marcus Schiesser 7562cb48d6 docs: changeset 2024-10-14 13:41:22 +07:00
Marcus Schiesser 9dde6d0288 feat: simplify questions asked (#370) 2024-10-14 13:35:39 +07:00
Thuc Pham 98a82b0b25 docs: chroma env variables (#367) 2024-10-11 11:10:29 +07:00
github-actions[bot] 7db72b6f2e Release 0.2.19 (#365)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-10 18:41:25 +07:00
Thuc Pham 3d41488301 feat: use selected llamacloud for multiagent (#359) 2024-10-10 18:37:55 +07:00
github-actions[bot] 1ee05eaf4b Release 0.2.18 (#364)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-10 18:03:43 +07:00
Huu Le 75e1f6104c fix: TypeScript templates do not create a new LlamaCloud index or upload a file to an existing index. (#356) 2024-10-10 17:58:12 +07:00
Huu Le 88220f1dd2 feat: add canceling workflow for multiagent (#361) 2024-10-10 15:24:43 +07:00
github-actions[bot] 6304114ef5 Release 0.2.17 (#357)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-09 16:31:50 +07:00
Marcus Schiesser 6335de1174 docs: changeset 2024-10-09 16:18:11 +07:00
Huu Le b9184ff59a fix: (FastAPI) Using LlamaCloud parameters does not use the configured value in the environment. (#358) 2024-10-09 16:13:35 +07:00
Thuc Pham cd3fcd0512 bump: use latest LITS (#343) 2024-10-09 13:40:04 +07:00
github-actions[bot] a47d778602 Release 0.2.16 (#349)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-08 17:28:40 +07:00
Marcus Schiesser 7f4ac228ee Don't need to run generate script for LlamaCloud (#352) 2024-10-08 16:56:12 +07:00
Marcus Schiesser 5263bde8e7 feat: Use selected LlamaCloud index in multi-agent template (#350) 2024-10-08 16:54:14 +07:00
Huu Le 4dee65b93d add astral's uv tool to github action (#351) 2024-10-08 16:19:20 +07:00
Huu Le c60182a925 Add mypy checker (#346) 2024-10-08 15:17:38 +07:00
Marcus Schiesser 0e78ba4603 fix: .env not loaded on poetry run generate (#348)
--------
Co-authored-by: leehuwuj <leehuwuj@gmail.com>
2024-10-08 13:41:37 +07:00
github-actions[bot] 7652b2b388 Release 0.2.15 (#342)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-07 16:37:05 +07:00
Huu Le d18f0399e5 feat: Add e2b code artifact tool support for the FastAPI template (#339) 2024-10-07 14:47:44 +07:00
Huu Le 3790ca0250 feat: add task selector to TS multiagent and revise the prompt (#336) 2024-10-07 10:23:21 +07:00
Huu Le 16e6124db2 bump llama-index-callbacks-arize-phoenix package and add test (#340) 2024-10-07 10:16:42 +07:00
github-actions[bot] 51dc0e4334 Release 0.2.14 (#337)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-03 17:14:02 +07:00
Thuc Pham 5a7216e36d feat: implement artifact tool in TS (#328)
---------

Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-10-03 17:13:17 +07:00
github-actions[bot] 27a1b9fdf2 Release 0.2.13 (#335)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-02 17:45:23 +07:00
Huu Le 04ddebcd64 feat: Add publisher agent, merge code with streaming template (#324)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-10-02 17:44:33 +07:00
Marcus Schiesser 3e8057a83a improve saveDocument 2024-10-01 16:22:22 +07:00
Marcus Schiesser 12ed570a53 refactor: make saveDocument reusable (#332) 2024-10-01 12:39:42 +07:00
Marcus Schiesser bde3daae08 reorganize e2e tests (split Python and TS) (#329)
---------
Co-authored-by: leehuwuj <leehuwuj@gmail.com>
2024-10-01 11:50:21 +07:00
github-actions[bot] 727eb105ea Release 0.2.12 (#327)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-27 15:17:08 +07:00
Thuc Pham ef070c0b4b feat: support multi agent for ts (#300)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-09-26 17:11:49 +07:00
Thuc Pham 70f7dcacc8 feat: add test deps for llamaparse (#323) 2024-09-26 09:49:40 +07:00
github-actions[bot] cf65162bef Release 0.2.11 (#325)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-25 16:26:35 +07:00
Thuc Pham 7c2a3f69a7 fix: postgres import (#322) 2024-09-25 16:24:14 +07:00
github-actions[bot] c7b7672062 Release 0.2.10 (#320)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-25 11:08:38 +07:00
Huu Le cb8d535d9b fix: don't write the StopEvent from sub task to the stream (#319) 2024-09-25 08:58:47 +07:00
github-actions[bot] 811cb13cba Release 0.2.9 (#317)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-24 16:18:08 +07:00
Marcus Schiesser 0213fe07dd fix: add dependencies for pg vector store (#312) 2024-09-24 16:11:43 +07:00
github-actions[bot] b31fa80326 Release 0.2.8 (#306)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-23 13:27:00 +07:00
Huu Le 40c5c8412c feat: add test and fix python dependencies (#304)
---------

Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-09-23 13:02:29 +07:00
Huu Le 0031e674c9 Support llama-index@^0.11.11 for multi-agent template (#305) 2024-09-23 09:37:13 +07:00
Marcus Schiesser 6e9184dd37 feat: use LlamaIndexAdapter (#302) 2024-09-20 16:08:08 +07:00
github-actions[bot] fa28cb5d0d Release 0.2.7 (#293)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-19 15:49:39 +07:00
Thuc Pham 8c1087f5f1 feat: enhance style for markdown (#298) 2024-09-18 11:37:56 +07:00
Huu Le 27333973f1 fixed llama-index-core with 0.11.9 (#296) 2024-09-18 11:26:43 +07:00
Marcus Schiesser cf3ec97a4c Dynamically select model for Groq (#278)
---------
Co-authored-by: Jac-Zac <jacopozac@icloud.com>
Co-authored-by: Thuc Pham <51660321+thucpn@users.noreply.github.com>
2024-09-18 09:29:10 +07:00
Thuc Pham 505b8e944a bump: use latest ai package version (#292) 2024-09-16 17:49:58 +07:00
github-actions[bot] 578f7f9e50 Release 0.2.6 (#288)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-13 18:58:55 +07:00
Thuc Pham adc40cf770 fix: vercel ai update crash sending annotations (#287)
* fix: vercel ai update crash sending annotations

* Create five-ties-happen.md
2024-09-13 18:55:46 +07:00
github-actions[bot] 7bce7386d5 Release 0.2.5 (#285)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-12 13:53:28 +07:00
Huu Le c011455dc4 fix cannot upload file (#286) 2024-09-12 13:51:48 +07:00
Thuc Pham 38a8be8d12 fix: filter in mongo vector store (#269) 2024-09-12 11:34:54 +07:00
github-actions[bot] 6e70eb4d11 Release 0.2.4 (#284)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-10 10:32:14 +07:00
Huu Le 917e862202 chore: fix ts syntax (#283) 2024-09-10 10:17:29 +07:00
github-actions[bot] e363bfeecc Release 0.2.3 (#281)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-09 17:18:40 +07:00
Huu Le b6da3c2419 chore: Always use file loader as default loader (#279) 2024-09-09 17:07:04 +07:00
github-actions[bot] 71fbe1b18f Release 0.2.2 (#277)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-09 14:41:21 +07:00
Huu Le 8105c5cf06 feat: Make suggest next questions configurable (#275)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-09-09 14:39:36 +07:00
github-actions[bot] c16deed864 Release 0.2.1 (#274)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-06 13:15:00 +07:00
Huu Le 6a409cbbc6 chore: bump tool package versions (#273) 2024-09-06 13:12:42 +07:00
github-actions[bot] a1892bef26 Release 0.2.0 (#272)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-05 12:18:42 +07:00
Marcus Schiesser 2f7e0220b5 docs: update changeset 2024-09-05 12:15:34 +07:00
Marcus Schiesser 435109fef0 feat: add multi-agents template based on workflows (#271)
---------
Co-authored-by: Thuc Pham <51660321+thucpn@users.noreply.github.com>
2024-09-05 12:13:39 +07:00
github-actions[bot] b1f3d5222f Release 0.1.44 (#266)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-29 16:01:59 +07:00
Marcus Schiesser e2c61884ef docs: improved wording 2024-08-29 15:55:27 +07:00
Thuc Pham fd4abb3bdd fix: keep origin upload filename (#268) 2024-08-29 15:47:50 +07:00
Huu Le bedde2bf20 Use is_empty filter (#263) 2024-08-29 15:46:31 +07:00
Huu Le 5cd12fa90d bump create-llama to 0.11 and update event handler (#260) 2024-08-29 14:24:57 +07:00
Thuc Pham 72b71952aa fix: dont use props as state in chat suggestion component (#267) 2024-08-29 11:38:15 +07:00
Thuc Pham 2f8feabcba feat: simplify CLI interface (#265) 2024-08-28 17:28:29 +07:00
github-actions[bot] a8a8c247e2 Release 0.1.43 (#264)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-28 16:50:27 +07:00
Thuc Pham 4fa2b76f3d feat: implement citation for TS (#257) 2024-08-28 16:47:00 +07:00
Thuc Pham 4ead8e14c2 fix: update nextjs config (#262) 2024-08-28 16:22:33 +07:00
github-actions[bot] 90398400c6 Release 0.1.42 (#261)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-27 14:15:18 +07:00
Marcus Schiesser 8f670a935c fix: allow relative URL in docs (#259) 2024-08-27 14:14:17 +07:00
Marcus Schiesser f04f60d555 refactor: e2e tests (#256) 2024-08-26 11:39:15 +07:00
github-actions[bot] 1ffd3c915b Release 0.1.41 (#248)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-22 16:34:41 +07:00
Marcus Schiesser 57e7638083 feat: Use the retrieval defaults from LlamaCloud (#247) 2024-08-22 16:30:04 +07:00
Marcus Schiesser 22ac2cae61 fix: add progress for no vecdb for Python 2024-08-22 11:22:09 +07:00
github-actions[bot] 8077195601 Release 0.1.40 (#245)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-20 14:59:24 +07:00
Huu Le 8ce4a8513d feat: use Reflex UI for structured extract template (#209)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-08-20 14:33:23 +07:00
github-actions[bot] 1d93775f04 Release 0.1.39 (#243)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-19 16:33:07 +07:00
Thuc Pham 3fb93c7939 feat: use llamacloud pipeline in TS (#236)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-08-19 15:49:51 +07:00
github-actions[bot] e248dc56bc Release 0.1.38 (#242)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-16 10:58:56 +07:00
Marcus Schiesser bd5e39a390 fix: files in sub folders of 'data' are not displayed (#241) 2024-08-16 10:57:44 +07:00
github-actions[bot] de2c7523dd Release 0.1.37 (#239)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-15 14:52:27 +07:00
Huu Le 9fd832c8b0 feat: In-text citing (#175)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-08-15 13:52:51 +07:00
github-actions[bot] b2c76dc7b6 Release 0.1.36 (#238)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-15 11:02:00 +07:00
Thuc Pham 2b7a5d8797 fix: optional params in file upload API (#237)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-08-15 11:00:53 +07:00
Marcus Schiesser d93ec803f5 feat: add ruff (#235)
* fix: formatting

* fix: ruff --fix

* feat: add ruff to github action

* fix: remove E402 check for some files
2024-08-15 09:38:13 +07:00
github-actions[bot] a6023b695b Release 0.1.35 (#234)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-14 17:22:49 +07:00
Marcus Schiesser 81ef7f0f93 feat: use llamacloud pipeline for private files and generate script in Python (#226)
---------
Co-authored-by: Thuc Pham <51660321+thucpn@users.noreply.github.com>
2024-08-14 17:03:16 +07:00
github-actions[bot] 8faf9170cf Release 0.1.34 (#233)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-14 14:59:19 +07:00
Huu Le c49a5e1620 chore: update wrong env name, add error handling for next question (#232) 2024-08-14 14:39:14 +07:00
github-actions[bot] 8b2de431f2 Release 0.1.33 (#229)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-13 11:06:17 +07:00
Huu Le d746c75e49 feat: Add Weaviate vector store for Typescript templates (#228) 2024-08-13 10:56:02 +07:00
Laurie Voss c87978ab96 Point the repo to the current one (#227) 2024-08-13 10:51:04 +07:00
github-actions[bot] 26359a0ac9 Release 0.1.32 (#224)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-12 17:19:22 +07:00
Huu Le 4039d3d1ea refactor: include chat configuration router in FastAPI app (#225) 2024-08-12 17:17:22 +07:00
Huu Le 3ec5163304 feat: add Weaviate vector database support for Python (#223)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-08-12 16:25:26 +07:00
Thuc Pham 878cfc2ca1 refactor: make llamacloud selector resuable (#221) 2024-08-09 12:02:43 +07:00
github-actions[bot] 9b5835b71c Release 0.1.31 (#222)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-09 11:55:58 +07:00
Thuc Pham 04a9c71759 feat: cluster nodes in document (#217)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-08-09 11:54:50 +07:00
github-actions[bot] 0bfdbc1dfe Release 0.1.30 (#214)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-08 15:59:57 +07:00
Thuc Pham fbcaebcbcf fix: use modern module resolution for express (#219) 2024-08-08 09:51:13 +02:00
Thuc Pham b6dd7a9acb fix: always send chat data when submit message (#213) 2024-08-07 15:22:33 +02:00
Marcus Schiesser 09e3022ad6 feat: add LlamaTrace support (#216) 2024-08-07 15:21:44 +02:00
Marcus Schiesser 9f739b9834 refactor: cleaned e2e runner (#215) 2024-08-07 17:41:22 +07:00
Marcus Schiesser c06ec4f14c fix: imports for MongoDB 2024-08-07 11:01:00 +02:00
Marcus Schiesser e7d30b1c69 refactor: test frameworks and datasources via matrix (#211) 2024-08-05 23:50:00 +07:00
github-actions[bot] e974c8ef11 Release 0.1.29 (#210)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-05 22:24:37 +07:00
Thuc Pham 8890e27a14 feat: implement index selector for LlamaCloud (#200)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-08-05 22:18:20 +07:00
Marcus Schiesser 072e69b465 fix: deactive llamacloud tests 2024-08-05 13:49:09 +02:00
Huu Le 83a648df0a chore: add use window.ENV.BASE_URL as backendOrigin (#205)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-08-02 15:50:04 +07:00
github-actions[bot] dcf52abdba Release 0.1.28 (#206)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-02 15:38:27 +07:00
Marcus Schiesser 9a09e8c7e2 fix: Vercel deployment (by including WASM files) (#201) 2024-08-02 15:36:54 +07:00
github-actions[bot] a4a55239e9 Release 0.1.27 (#204)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-01 17:09:07 +02:00
Thuc Pham c5c7eee04d refactor: make components resuable for chat llm (#202)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-08-01 16:31:38 +02:00
github-actions[bot] 8b89ac547f Release 0.1.26 (#199)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-31 08:56:00 +02:00
Marcus Schiesser f43399cc18 fix: Add metadatafilters to context chat engine (Typescript) (#196) 2024-07-31 08:55:06 +02:00
github-actions[bot] df51361ca1 Release 0.1.25 (#195)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-30 15:09:21 +07:00
Huu Le c67daeb2be fix: missing set private to false for default generate.py (#194) 2024-07-30 15:06:04 +07:00
github-actions[bot] af6ac9a444 Release 0.1.24 (#188)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-29 21:01:51 +07:00
Marcus Schiesser 22245ca9fd chore: remove azure openai key question 2024-07-29 15:51:58 +02:00
Marcus Schiesser 81b67794ef fix: throw errors if azure deployments are no 2024-07-29 15:51:58 +02:00
Bartłomiej Szczygło 5c13646e55 Fix starter questions not working in python backend (#189)
* Fix starter questions not working in python backend

* add changeset

---------

Co-authored-by: Arputikos <arputikos11@op.pl>
Co-authored-by: leehuwuj <leehuwuj@gmail.com>
2024-07-29 15:55:00 +07:00
Thuc Pham 43474a51ff feat: config llamacloud organization ID (#192)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-07-29 15:20:18 +07:00
Huu Le cf11b233c6 feat: support using azure code interpreter in create-llama (#158)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
Co-authored-by: Wassim Chegham <github@wassim.dev>
Co-authored-by: Thuc Pham <51660321+thucpn@users.noreply.github.com>
2024-07-26 17:34:36 +07:00
Thuc Pham fd9fb42ace feat: add azure model provider (#184) 2024-07-26 17:32:14 +07:00
Thuc Pham 92798f73dd fix: dont ask useLlamaParse if --no-llama-parse in command (#187) 2024-07-26 14:58:34 +07:00
Marcus Schiesser e71d8bd6e2 fix: lint 2024-07-25 15:25:53 +02:00
github-actions[bot] e25e112873 Release 0.1.23 (#186)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-25 20:23:04 +07:00
Marcus Schiesser 048187cce3 Update README.md 2024-07-25 20:21:19 +07:00
Marcus Schiesser 6bd76fbfb1 feat: Add template for structured extraction (#185) 2024-07-25 19:56:26 +07:00
github-actions[bot] a553d5051e Release 0.1.22 (#180)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-25 15:57:55 +07:00
Thuc Pham b0becaa8dc feat: add e2e testing for llamacloud datasource (#181)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-07-25 15:56:23 +07:00
Marcus Schiesser 6a42542642 chore: sync tsconfig with create-next-app (#182) 2024-07-25 15:11:05 +07:00
Thuc Pham f936a470f3 fix: ignore ts check grammar for regex (#183) 2024-07-25 14:40:34 +07:00
Thuc Pham df9cca5a52 feat: upgrade pdf viewer (#179)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-07-24 16:03:58 +07:00
Marcus Schiesser dc9ee895a7 Add video to README.md 2024-07-23 16:38:24 +02:00
github-actions[bot] 98ff3c2e77 Release 0.1.21 (#169)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-23 19:01:32 +07:00
Huu Le 0900413689 Add next questions suggestion to the user (#170)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-07-23 17:04:53 +07:00
Marcus Schiesser 8dc6a2bf5a fix: simplify webpack config using 0.57 (#174) 2024-07-23 16:16:38 +07:00
Huu Le 23b735717d chore: Use gpt-4o-mini as default (#173) 2024-07-22 21:42:40 +07:00
Thuc Pham bd4714ca8d feat: add filter for query in ts templates (#172)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-07-22 21:42:20 +07:00
Thuc Pham 455ab6862e feat: display files from llamacloud (#153)
---------
Co-authored-by: leehuwuj <leehuwuj@gmail.com>
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-07-18 22:46:06 +07:00
Huu Le 58e6c150c0 feat: Use LlamaParse to parse the private files (#167) 2024-07-17 19:15:43 +07:00
Thuc Pham e57e9813dd fix: use stable tsup version (#168) 2024-07-17 17:07:40 +07:00
github-actions[bot] 7302880c5f Release 0.1.20 (#166)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-15 22:17:09 +07:00
Marcus Schiesser 624c721ac4 chore: update to llamaindex 0.10.55 (#165) 2024-07-15 21:51:15 +07:00
github-actions[bot] d2c66cf550 Release 0.1.19 (#163)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-12 15:36:26 +07:00
Huu Le df96159e88 feat: Use Qdrant FastEmbed as local embedding provider (#162) 2024-07-12 15:01:41 +07:00
Thuc Pham 32fb32ab18 feat: support uploading pdf, docx, txt (#140)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
Co-authored-by: leehuwuj <leehuwuj@gmail.com>
2024-07-12 14:56:11 +07:00
github-actions[bot] 3b57bdcf12 Release 0.1.18 (#157)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-09 20:08:04 +07:00
Huu Le a221cfc11f feat: use LlamaParse for all the supported types (#154) 2024-07-09 15:33:11 +07:00
Huu Le d3f92f8a69 bump llama-index version (#159) 2024-07-09 14:17:18 +07:00
Thuc Pham d1026ea784 feat: support mistral as llm and embedding (#155) 2024-07-05 16:58:10 +07:00
Huu Le 791ca7c945 bump llama_index version (#156) 2024-07-05 16:56:17 +07:00
github-actions[bot] 07fcefde5d Release 0.1.17 (#152)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-03 19:24:02 +07:00
Huu Le 9ecd061262 feat: Add llama-agent template (#150)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-07-03 16:59:16 +07:00
github-actions[bot] 344d832d3d Release 0.1.16 (#151)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-02 22:40:37 +07:00
Mohammad Amir a0aab03226 T-System's LLMHUB is added as model provider backend. (#139)
---------
Co-authored-by: Duc Anh Ho <ducanh.ho2296@gmail.com>
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-07-02 22:12:42 +07:00
github-actions[bot] a8073063c5 Release 0.1.15 (#148)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-28 22:12:20 +07:00
Thuc Pham aeb6fef4da feat: use LlamaCloud for TS/Python (#149)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-06-28 22:10:37 +07:00
Huu Le 64732f05aa Fix: remove sandbox link from openai models (#145) 2024-06-27 22:14:15 +07:00
github-actions[bot] 588e0d607b Release 0.1.14 (#144)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-26 17:34:20 +07:00
Marcus Schiesser f2c3389168 chore: update to llamaindex 0.4.3 (#143)
---------
Co-authored-by: Alex Yang <himself65@outlook.com>
2024-06-26 15:05:40 +07:00
Huu Le 5093b37c05 Add support for Linux (#142) 2024-06-25 15:05:14 +07:00
github-actions[bot] f383f0cbe9 Release 0.1.13 (#141)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-24 16:18:14 +07:00
Thuc Pham b3c969dae5 feat: image generator tool (#135)
---------
Co-authored-by: leehuwuj <leehuwuj@gmail.com>
2024-06-24 15:33:16 +07:00
github-actions[bot] 628e16df7c Release 0.1.12 (#136)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-21 16:55:01 +07:00
Marcus Schiesser aa69014d04 fix: make regex work for TS 5.2 2024-06-21 11:31:31 +02:00
github-actions[bot] 293557cbb4 Release 0.1.11 (#129)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-19 20:36:58 +07:00
Marcus Schiesser b46d050fc3 fix: format 2024-06-19 15:08:42 +02:00
Jacopo Zacchigna 02ed277dd0 Starting to add Groq as a provider (#131)
---------
Co-authored-by: Marcus Schiesser <marcus.schiesser@googlemail.com>
2024-06-19 17:43:36 +07:00
Huu Le 48b96ff188 feat: add DuckDuckGo search tool (#133) 2024-06-19 16:29:16 +07:00
Huu Le 9c9decbb88 Reuse function tool instance and improve e2b interpreter tool (#127)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-06-14 16:04:05 +07:00
Huu Le 0748f2e8d7 remove gemini model map (#128) 2024-06-14 09:18:23 +02:00
github-actions[bot] 3079162806 Release 0.1.10 (#122)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-12 20:59:11 +07:00
Marcus Schiesser 48c19c6e62 fix: impove OpenAPI tool for TS 2024-06-12 15:28:59 +02:00
Thuc Pham d75c08e7d8 feat: make chat-session component independence from container (#124) 2024-06-12 19:02:58 +07:00
Huu Le 8f03f8d4bc chore: Improve fastapi (#123) 2024-06-12 16:50:20 +07:00
Marcus Schiesser 19c57d945a fix: reverse config hint 2024-06-12 10:46:50 +02:00
Thuc Pham 9112d0801e feat: implement openapi action tool for ts (#108)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-06-10 19:40:09 +07:00
Thuc Pham 93b797c162 refactor: structure fe components (#121) 2024-06-10 17:02:25 +07:00
github-actions[bot] d53b760fd0 Release 0.1.9 (#101)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-07 22:56:34 +07:00
Marcus Schiesser a880c7c016 chore: update llamaindex@0.3.16 2024-06-07 17:40:39 +02:00
Marcus Schiesser 7b116ce7f7 fix: allow subsequent tool calls 2024-06-07 17:35:23 +02:00
Marcus Schiesser d1232fb1d5 fix: log interpreter tool error 2024-06-07 16:10:33 +02:00
Marcus Schiesser bedf199236 fix: throw and show error if unsupported annotation (e.g. image) is uploaded 2024-06-07 15:30:31 +02:00
Marcus Schiesser c1510bd3fa fix: remove redundant config info 2024-06-07 14:37:08 +02:00
Huu Le 69b9ce76bf refactor code (#119) 2024-06-07 13:46:25 +02:00
Marcus Schiesser 9ced116e1a refactor: use message annotations instead of sending data (#116)
---------
Co-authored-by: Thuc Pham <51660321+thucpn@users.noreply.github.com>
Co-authored-by: leehuwuj <leehuwuj@gmail.com>
2024-06-07 17:14:15 +07:00
Huu Le fae9bcd65a add raw text e2b tool output response (#115) 2024-06-06 13:23:31 +02:00
Thuc Pham 2091fea2b4 feat: display attachments in user messages (#114)
* use same csv card for message and upload box
* do not send csv and image data back to client
* fix: use LLM_MAX_TOKENS
---------

Co-authored-by: leehuwuj <leehuwuj@gmail.com>
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-06-06 14:24:31 +07:00
Huu Le 563b51d76d Fix: Vercel streaming (python) does not stream data events instantly (#111) 2024-06-05 15:54:55 +07:00
Thuc Pham 88c88bf16d fix: logo overlay text input because of hegiht (#112) 2024-06-05 15:40:38 +07:00
Marcus Schiesser cd6ebf7295 dx: add hint if tool config is needed 2024-06-04 12:20:52 +02:00
Marcus Schiesser 50b2ddbbf5 docs: updated changeset 2024-06-04 11:15:47 +02:00
Huu Le 5fe2d519d2 chore: Add Azure OpenAI model provider python (#110) 2024-06-04 16:14:21 +07:00
Huu Le 09f1db3b5e feat: Support uploading CSV files for FastAPI app (#109) 2024-06-04 14:23:25 +07:00
Thuc Pham cb3be7d1d4 feat: display conversation starter from backend env (#104)
* feat: display conversation starter from frontend env

* use nextjs config api

* update to /api/chat/config

* add config api for express

* add api config for fast api

* Create ten-badgers-learn.md

* remove default conversation staters

* check empty string

* update pydantic docs

* refactor: move NEXT_PUBLIC_CHAT_API to use config

* use config to get chatAPI

* refactor: rename useClientConfig
2024-06-01 09:57:17 +07:00
Thuc Pham 5474a1f182 feat: enhance csv upload feature (#105)
* remove all multiModal props

* hide uploaded csv files if choose a new one

* feat: support multiple csv upload and reuse

* rename type and make it scrollable
2024-06-01 09:37:46 +07:00
Huu Le 1148ddba53 bump llama-index-agent-openai version to 0.2.6 (#107) 2024-05-31 13:46:35 +01:00
Huu Le 9e945ed355 bump llama_index and gemini version (#106) 2024-05-31 15:12:14 +07:00
Thuc Pham 6342163df2 Merge pull request #103 from run-llama/feat/add-openapi-tool
feat: Add OpenAPI Action tool
2024-05-30 15:33:36 +07:00
Thuc Pham a42fa53a6b feat: implement csv upload (#96)
* feat: implement interpreter tool

* build tool system prompt

* refactor: use local file system, use absolute resource url

* fix: typo

* feat: implement csv upload

* remove dead code

* fix lint

* update icon & fix code review

* fix lint

* Update .gitignore

* Update pre-commit

* add timeout for streaming

* Create bright-turkeys-melt.md

* remove multi modal prop

* suggest csv resources from frontend annotation data

* get resouces inside chat input

* resolve conflict

* update convert message content

* fix lint

* feat: limit display

---------

Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2024-05-30 10:38:54 +07:00
leehuwuj 099f626586 use urlparse for file path 2024-05-30 10:05:00 +07:00
leehuwuj 956538eeb0 add changeset 2024-05-30 09:27:21 +07:00
leehuwuj 555f6b2905 refactor code 2024-05-30 09:25:56 +07:00
leehuwuj d8bc271a21 add local tool that combine openapi and request tool 2024-05-30 09:11:21 +07:00
leehuwuj f29561cde2 add cache to toolfactory load_tools 2024-05-29 10:40:40 +07:00
leehuwuj 442abae8ac add openapi tool and http request tool 2024-05-29 08:40:16 +07:00
Huu Le 0ad2207684 Merge pull request #98 from run-llama/feat/construct-resource-url-from-backend
feat: construct resource url from backend
2024-05-28 20:43:04 +07:00
Thuc Pham bfde30deed move logger to global scope 2024-05-28 18:42:46 +07:00
Thuc Pham 96fdb83abf use logger warning 2024-05-28 18:33:53 +07:00
Huu Le b7e0072c9c chore: always generate tools config if user selects agent mode (#102) 2024-05-28 14:35:36 +07:00
Thuc Pham 81bc340dda add warning when no file server url prefix 2024-05-27 18:21:32 +07:00
Thuc Pham ddf3aef7dc remove node path 2024-05-27 18:20:27 +07:00
Thuc Pham 1f5a26f3a8 Merge pull request #100 from run-llama/feat/code-interpreter-python
feat: add support for FastAPI in code interpreter tool
2024-05-27 16:58:32 +07:00
Thuc Pham 48188ca3f9 feat: construct resource url from backend 2024-05-24 14:40:44 +07:00
642 changed files with 59801 additions and 9573 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"create-llama": patch
---
Add support E2B code interpreter tool for FastAPI
+6
View File
@@ -0,0 +1,6 @@
# coderabbit.yml
reviews:
path_instructions:
- path: "templates/**"
instructions: |
For files under the `templates` folder, do not report 'Missing Dependencies Detected' errors.
-12
View File
@@ -1,12 +0,0 @@
{
"extends": [
"prettier"
],
"rules": {
"max-params": [
"error",
4
],
"prefer-const": "error",
},
}
+116 -21
View File
@@ -1,23 +1,28 @@
name: E2E Tests
name: E2E Tests for create-llama package
on:
push:
branches: [main]
paths-ignore:
- "python/llama-index-server/**"
- ".github/workflows/*llama_index_server.yml"
pull_request:
branches: [main]
env:
POETRY_VERSION: "1.6.1"
paths-ignore:
- "python/llama-index-server/**"
- ".github/workflows/*llama_index_server.yml"
jobs:
e2e:
name: create-llama
e2e-python:
name: python
timeout-minutes: 60
strategy:
fail-fast: true
matrix:
node-version: [18, 20]
node-version: [20]
python-version: ["3.11"]
os: [macos-latest, windows-latest]
os: [macos-latest, windows-latest, ubuntu-22.04]
frameworks: ["fastapi"]
vectordbs: ["none", "llamacloud"]
defaults:
run:
shell: bash
@@ -30,10 +35,10 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: ${{ env.POETRY_VERSION }}
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Add uv to PATH # Ensure uv is available in subsequent steps
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- uses: pnpm/action-setup@v3
@@ -48,25 +53,115 @@ jobs:
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
working-directory: .
working-directory: packages/create-llama
- name: Build create-llama
run: pnpm run build
working-directory: .
working-directory: packages/create-llama
- name: Install
run: pnpm run pack-install
working-directory: .
working-directory: packages/create-llama
- name: Run Playwright tests
run: pnpm run e2e
- name: Build and store server package
run: |
pnpm run build
wheel_file=$(ls dist/*.whl | head -n 1)
mkdir -p "${{ runner.temp }}"
cp "$wheel_file" "${{ runner.temp }}/"
echo "SERVER_PACKAGE_PATH=${{ runner.temp }}/$(basename "$wheel_file")" >> $GITHUB_ENV
working-directory: python/llama-index-server
- name: Run Playwright tests for Python
run: pnpm run e2e:python
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
working-directory: .
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
FRAMEWORK: ${{ matrix.frameworks }}
VECTORDB: ${{ matrix.vectordbs }}
PYTHONIOENCODING: utf-8
PYTHONLEGACYWINDOWSSTDIO: utf-8
SERVER_PACKAGE_PATH: ${{ env.SERVER_PACKAGE_PATH }}
working-directory: packages/create-llama
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: ./playwright-report/
name: playwright-report-python-${{ matrix.os }}-${{ matrix.frameworks }}-${{ matrix.vectordbs }}
path: packages/create-llama/playwright-report/
overwrite: true
retention-days: 30
e2e-typescript:
name: typescript
timeout-minutes: 60
strategy:
fail-fast: true
matrix:
node-version: [22]
os: [macos-latest, windows-latest, ubuntu-22.04]
frameworks: ["nextjs"]
vectordbs: ["none", "llamacloud"]
defaults:
run:
shell: bash
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
working-directory: packages/create-llama
- name: Build create-llama
run: pnpm run build
working-directory: packages/create-llama
- name: Install
run: pnpm run pack-install
working-directory: packages/create-llama
- name: Build server
run: pnpm run build
working-directory: packages/server
- name: Pack @llamaindex/server package
run: |
pnpm pack --pack-destination "${{ runner.temp }}"
if [ "${{ runner.os }}" == "Windows" ]; then
file=$(find "${{ runner.temp }}" -name "llamaindex-server-*.tgz" | head -n 1)
mv "$file" "${{ runner.temp }}/llamaindex-server.tgz"
else
mv ${{ runner.temp }}/llamaindex-server-*.tgz ${{ runner.temp }}/llamaindex-server.tgz
fi
working-directory: packages/server
- name: Run Playwright tests for TypeScript
run: |
pnpm run e2e:ts
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
FRAMEWORK: ${{ matrix.frameworks }}
VECTORDB: ${{ matrix.vectordbs }}
SERVER_PACKAGE_PATH: ${{ runner.temp }}/llamaindex-server.tgz
working-directory: packages/create-llama
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-typescript-${{ matrix.os }}-${{ matrix.frameworks }}-${{ matrix.vectordbs}}-node${{ matrix.node-version }}
path: packages/create-llama/playwright-report/
overwrite: true
retention-days: 30
@@ -16,6 +16,16 @@ jobs:
- uses: pnpm/action-setup@v3
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Setup Node.js
uses: actions/setup-node@v4
with:
@@ -30,3 +40,22 @@ jobs:
- name: Run Prettier
run: pnpm run format
- name: Run build
run: pnpm run build
- name: Run Typecheck for examples
run: pnpm run typecheck
working-directory: packages/server/examples
- name: Run Python format check
uses: chartboost/ruff-action@v1
with:
args: "format --check"
src: "python/llama-index-server"
- name: Run Python lint
uses: chartboost/ruff-action@v1
with:
args: "check"
src: "python/llama-index-server"
+12
View File
@@ -17,6 +17,14 @@ jobs:
- uses: pnpm/action-setup@v3
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install uv
uses: astral-sh/setup-uv@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
@@ -48,8 +56,12 @@ jobs:
with:
commit: Release ${{ steps.get-changeset-status.outputs.new-version }}
title: Release ${{ steps.get-changeset-status.outputs.new-version }}
# bump versions
version: pnpm new-version
# build package and call changeset publish
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
@@ -0,0 +1,136 @@
name: Build Package
on:
pull_request:
env:
PYTHON_VERSION: "3.9"
UI_TEST: "true"
jobs:
unit-test:
name: Unit Tests
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: python/llama-index-server
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.9"]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Install dependencies
shell: bash
run: pnpm install && pnpm build
- name: Run unit tests
shell: bash
run: uv run pytest tests
type-check:
name: Type Check
runs-on: ubuntu-latest
defaults:
run:
working-directory: python/llama-index-server
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Install dependencies
run: pnpm install
- name: Run mypy
shell: bash
run: uv run mypy llama_index
build:
needs: [unit-test, type-check]
runs-on: ubuntu-latest
defaults:
run:
working-directory: python/llama-index-server
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Install dependencies
run: pnpm install && pnpm build
- name: Build package
shell: bash
run: uv build
- name: Get the absolute wheel file path and save it to the output
shell: bash
id: get_whl_path
run: |
WHL_FILE=$(readlink -f dist/*.whl)
echo "whl_file=$WHL_FILE" >> $GITHUB_OUTPUT
- name: Test import
shell: bash
working-directory: ${{ github.workspace }}
env:
WHL_FILE: ${{ steps.get_whl_path.outputs.whl_file }}
run: |
uv run --with $WHL_FILE python -c "from llama_index.server import LlamaIndexServer"
- name: Check frontend resources is present
shell: bash
working-directory: ${{ github.workspace }}
env:
WHL_FILE: ${{ steps.get_whl_path.outputs.whl_file }}
run: |
uv run --with $WHL_FILE python -c "from llama_index.server.chat_ui import check_ui_resources; check_ui_resources()"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: llama-index-server
path: dist/
+3 -14
View File
@@ -6,9 +6,6 @@ node_modules
.pnpm-store
.pnp.js
# testing
coverage
# next.js
.next/
out/
@@ -34,17 +31,9 @@ yarn-error.log*
dist/
lib/
# e2e
.cache
test-results/
playwright-report/
blob-report/
playwright/.cache/
.tsbuildinfo
e2e/cache
# intellij
**/.idea
# build artifacts
create-llama-*.tgz
# vscode
.vscode
!.vscode/settings.json
+2
View File
@@ -1,2 +1,4 @@
pnpm format
pnpm lint
uvx ruff check .
uvx ruff format . --check
+15 -3
View File
@@ -1,6 +1,18 @@
apps/docs/i18n
apps/docs/docs/api
node_modules/
pnpm-lock.yaml
lib/
dist/
.docusaurus/
cache/
build/
.next/
out/
packages/server/server/
packages/server/project/
**/playwright-report/
**/test-results/
# Python
python/
**/*.mypy_cache/**
**/*.venv/**
**/*.ruff_cache/**
-286
View File
@@ -1,286 +0,0 @@
# create-llama
## 0.1.8
### Patch Changes
- cd50a33: Add interpreter tool for TS using e2b.dev
## 0.1.7
### Patch Changes
- 260d37a: Add system prompt env variable for TS
- bbd5b8d: Fix postgres connection leaking issue
- bb53425: Support HTTP proxies by setting the GLOBAL_AGENT_HTTP_PROXY env variable
- 69c2e16: Fix streaming for Express
- 7873bfb: Update Ollama provider to run with the base URL from the environment variable
## 0.1.6
### Patch Changes
- 56537a1: Display PDF files in source nodes
## 0.1.5
### Patch Changes
- 84db798: feat: support display latex in chat markdown
## 0.1.4
### Patch Changes
- 0bc8e75: Use ingestion pipeline for dedicated vector stores (Python only)
- cb1001d: Add ChromaDB vector store
## 0.1.3
### Patch Changes
- 416073d: Directly import vector stores to work with NextJS
## 0.1.2
### Patch Changes
- 056e376: Add support for displaying tool outputs (including weather widget as example)
## 0.1.1
### Patch Changes
- 7bd3ed5: Support Anthropic and Gemini as model providers
- 7bd3ed5: Support new agents from LITS 0.3
- cfb5257: Display events (e.g. retrieving nodes) per chat message
## 0.1.0
### Minor Changes
- f1c3e8d: Add Llama3 and Phi3 support using Ollama
### Patch Changes
- a0dec80: Use `gpt-4-turbo` model as default. Upgrade Python llama-index to 0.10.28
- 753229d: Remove asking for AI models and use defaults instead (OpenAIs GPT-4 Vision Preview and Embeddings v3). Use `--ask-models` CLI parameter to select models.
- 1d78202: Add observability for Python
- 6acccd2: Use poetry run generate to generate embeddings for FastAPI
- 9efcffe: Use Settings object for LlamaIndex configuration
- 418bf9b: refactor: use tsx instead of ts-node
- 1be69a5: Add Qdrant support
## 0.0.32
### Patch Changes
- 625ed4d: Support Astra VectorDB
- 922e0ce: Remove UI question (use shadcn as default). Use `html` UI by calling create-llama with --ui html parameter
- ce2f24d: Update loaders and tools config to yaml format (for Python)
- e8db041: Let user select multiple datasources (URLs, files and folders)
- c06d4af: Add nodes to the response (Python)
- 29b17ee: Allow using agents without any data source
- 665c26c: Add redirect to documentation page when accessing the base URL (FastAPI)
- 78ded9e: Add Dockerfile templates for Typescript and Python
- 99e758f: Merge non-streaming and streaming template to one
- b3f2685: Add support for agent generation for Typescript
- 2739714: Use a database (MySQL or PostgreSQL) as a data source
## 0.0.31
### Patch Changes
- 56faee0: Added windows e2e tests
- 60ed8fe: Added missing environment variable config for URL data source
- 60ed8fe: Fixed tool usage by freezing llama-index package versions
## 0.0.30
### Patch Changes
- 3af6328: Add support for llamaparse using Typescript
- dd92b91: Add fetching llm and embedding models from server
- bac1b43: Add Milvus vector database
## 0.0.29
### Patch Changes
- edd24c2: Add observability with openllmetry
- 403fc6f: Minor bug fixes to improve DX (missing .env value and updated error messages)
- 0f79757: Ability to download community submodules
## 0.0.28
### Patch Changes
- 89a49f4: Add more config variables to .env file
- fdf48dd: Add "Start in VSCode" option to postInstallAction
- fdf48dd: Add devcontainers to generated code
## 0.0.27
### Patch Changes
- 2d29350: Add LlamaParse option when selecting a pdf file or a folder (FastAPI only)
- b354f23: Add embedding model option to create-llama (FastAPI only)
## 0.0.26
### Patch Changes
- 09d532e: feat: generate llama pack project from llama index
- cfdd6db: feat: add pinecone support to create llama
- ef25d69: upgrade llama-index package to version v0.10.7 for create-llama app
- 50dfd7b: update fastapi for CVE-2024-24762
## 0.0.25
### Patch Changes
- d06a85b: Add option to create an agent by selecting tools (Google, Wikipedia)
- 7b7329b: Added latest turbo models for GPT-3.5 and GPT 4
## 0.0.24
### Patch Changes
- ba95ca3: Use condense plus context chat engine for FastAPI as default
## 0.0.23
### Patch Changes
- c680af6: Fixed issues with locating templates path
## 0.0.22
### Patch Changes
- 6dd401e: Add an option to provide an URL and chat with the website data (FastAPI only)
- e9b87ef: Select a folder as data source and support more file types (.pdf, .doc, .docx, .xls, .xlsx, .csv)
## 0.0.20
### Patch Changes
- 27d55fd: Add an option to provide an URL and chat with the website data
## 0.0.19
### Patch Changes
- 3a29a80: Add node_modules to gitignore in Express backends
- fe03aaa: feat: generate llama pack example
## 0.0.18
### Patch Changes
- 88d3b41: fix packaging
## 0.0.17
### Patch Changes
- fa17f7e: Add an option that allows the user to run the generated app
- 9e5d8e1: Add an option to select a local PDF file as data source
## 0.0.16
### Patch Changes
- a73942d: Fix: Bundle mongo dependency with NextJS
- 9492cc6: Feat: Added option to automatically install dependencies (for Python and TS)
- f74dea5: Feat: Show images in chat messages using GPT4 Vision (Express and NextJS only)
## 0.0.15
### Patch Changes
- 8e124e5: feat: support showing image on chat message
## 0.0.14
### Patch Changes
- 2e6b36e: fix: re-organize file structure
- 2b356c8: fix: relative path incorrect
## 0.0.13
### Patch Changes
- Added PostgreSQL vector store (for Typescript and Python)
- Improved async handling in FastAPI
## 0.0.12
### Patch Changes
- 9c5e22a: Added cross-env so frontends with Express/FastAPI backends are working under Windows
- 5ab65eb: Bring Python templates with TS templates to feature parity
- 9c5e22a: Added vector DB selector to create-llama (starting with MongoDB support)
## 0.0.11
### Patch Changes
- 2aeb341: - Added option to create a new project based on community templates
- Added OpenAI model selector for NextJS projects
- Added GPT4 Vision support (and file upload)
## 0.0.10
### Patch Changes
- Bugfixes (thanks @marcusschiesser)
## 0.0.9
### Patch Changes
- acfe232: Deployment fixes (thanks @seldo)
## 0.0.8
### Patch Changes
- 8cdb07f: Fix Next deployment (thanks @seldo and @marcusschiesser)
## 0.0.7
### Patch Changes
- 9f9f293: Added more to README and made it easier to switch models (thanks @seldo)
## 0.0.6
### Patch Changes
- 4431ec7: Label bug fix (thanks @marcusschiesser)
## 0.0.5
### Patch Changes
- 25257f4: Fix issue where it doesn't find OpenAI Key when running npm run generate (#182) (thanks @RayFernando1337)
## 0.0.4
### Patch Changes
- 031e926: Update create-llama readme (thanks @logan-markewich)
## 0.0.3
### Patch Changes
- 91b42a3: change version (thanks @marcusschiesser)
## 0.0.2
### Patch Changes
- e2a6805: Hello Create Llama (thanks @marcusschiesser)
+201
View File
@@ -0,0 +1,201 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Repository Overview
Create-llama is a monorepo containing CLI tools and server frameworks for building LlamaIndex-powered applications. The repository combines TypeScript/Node.js and Python components in a unified development environment.
## Architecture
### Monorepo Structure
- **`packages/create-llama/`**: Main CLI tool for scaffolding LlamaIndex applications
- **`packages/server/`**: TypeScript/Next.js server framework (`@llamaindex/server`)
- **`python/llama-index-server/`**: Python/FastAPI server framework
- **Root**: Workspace configuration and shared development tools
### Key Technologies
- **Package Manager**: pnpm with workspace configuration
- **Build Tools**: bunchee (TypeScript), Next.js, hatchling (Python)
- **Testing**: Playwright for e2e, pytest for Python
- **Version Management**: changesets for TypeScript packages, manual for Python
## Development Commands
### Root Level (Monorepo)
```bash
pnpm dev # Start all packages in development mode
pnpm build # Build all packages
pnpm lint # ESLint across TypeScript packages
pnpm format # Prettier formatting
pnpm e2e # Run end-to-end tests
```
### Create-llama Package
```bash
cd packages/create-llama
npm run build # Build CLI using bash script and ncc
npm run dev # Watch mode development
npm run e2e # Playwright tests for generated projects
npm run clean # Clean build artifacts and template caches
```
### TypeScript Server Package
```bash
cd packages/server
pnpm dev # Watch mode with bunchee
pnpm build # Multi-step build: ESM/CJS + Next.js + static assets
pnpm clean # Clean all build outputs
```
### Python Server Package
```bash
cd python/llama-index-server
uv run generate # Index data files
fastapi dev # Start development server with hot reload
pytest # Run test suite
```
## Template System
The CLI uses a sophisticated template system in `packages/create-llama/templates/`:
### Organization
- **`types/`**: Base project structures (streaming, reflex, llamaindexserver)
- **`components/`**: Reusable components across frameworks
- `engines/` - Chat and agent engines
- `loaders/` - File, web, database loaders
- `providers/` - AI model configurations
- `vectordbs/` - Vector database integrations
- `use-cases/` - Workflow implementations
### Development Workflow
- Templates support multiple frameworks (Next.js, Express, FastAPI)
- Component system allows mix-and-match functionality
- E2E tests validate generated projects work correctly
## Server Framework Architecture
### TypeScript Server (`@llamaindex/server`)
- **Core**: `LlamaIndexServer` class wrapping Next.js with workflow support
- **Frontend**: React-based chat UI with shadcn/ui components
- **API**: `/api/chat` endpoint with streaming responses
- **Build Process**: Complex multi-step build including static assets for Python integration
### Python Server (`llama-index-server`)
- **Core**: `LlamaIndexServer` class extending FastAPI
- **Architecture**: Workflow factory pattern for stateless request handling
- **UI Generation**: AI-powered React component generation from Pydantic schemas
- **Development**: Hot reloading support with dev mode
## Common Patterns
### Workflow Integration
Both server frameworks use factory patterns:
```typescript
// TypeScript
const server = new LlamaIndexServer({
workflow: (context) => createWorkflow(context)
});
// Python
def create_workflow(chat_request: ChatRequest) -> Workflow:
return MyWorkflow(chat_request.messages)
```
### Event System
Structured events for UI communication:
- **UIEvent**: Custom components with Pydantic/Zod schemas
- **ArtifactEvent**: Code/documents for Canvas panel
- **SourceNodesEvent**: Document sources with metadata
- **AgentRunEvent**: Tool usage and progress tracking
### File Handling
- Both servers auto-mount `data/` and `output/` directories
- LlamaCloud integration for remote file access
- Static file serving through framework-specific methods
## Testing Strategy
### E2E Testing
- Playwright tests in `packages/create-llama/e2e/`
- Tests both Python and TypeScript generated projects
- Validates CLI generation and application functionality
### Unit Testing
- Python: pytest with comprehensive API and service tests
- TypeScript: Integrated testing through build process
## Build Process
### Create-llama CLI
1. TypeScript compilation with bash script
2. ncc bundling for standalone executable
3. Template validation and caching
### Server Package Build
1. **prebuild**: Clean directories
2. **build**: bunchee compilation to ESM/CJS
3. **postbuild**: Next.js preparation and static asset generation
4. **prepare:py-static**: Python integration assets
### Release Process
```bash
pnpm release # Build all + publish npm packages + Python release
```
## Development Environment Setup
### Prerequisites
- Node.js >=16.14.0
- Python with uv package manager
- pnpm for package management
### Common Workflow
1. Clone repository and run `pnpm install`
2. For CLI development: work in `packages/create-llama/`
3. For server development: choose TypeScript or Python package
4. Use `pnpm dev` for concurrent development across packages
5. Run `pnpm e2e` to validate changes with generated projects
## Special Considerations
### Template Development
- Changes to templates require rebuilding CLI
- E2E tests validate template functionality across frameworks
- Template caching system speeds up repeated builds
### Cross-package Dependencies
- Server package builds static assets for Python integration
- Version synchronization between TypeScript and Python packages
- Shared UI components and styling across implementations
### Performance
- CLI uses caching for template operations
- Server frameworks support streaming responses
- Background processing for file operations and LlamaCloud integration
+39 -54
View File
@@ -1,14 +1,20 @@
# Create LlamaIndex App
# Create Llama
The easiest way to get started with [LlamaIndex](https://www.llamaindex.ai/) is by using `create-llama`. This CLI tool enables you to quickly start building a new LlamaIndex application, with everything set up for you.
## Get started
Just run
```bash
npx create-llama@latest
```
to get started, or see below for more options. Once your app is generated, run
to get started, or watch this video for a demo session:
<img src="https://github.com/user-attachments/assets/c4a7fe18-8e30-498a-96f8-78127dd706b9" width="100%">
Once your app is generated, run
```bash
npm run dev
@@ -18,23 +24,24 @@ to start the development server. You can then visit [http://localhost:3000](http
## What you'll get
- A Next.js-powered front-end using components from [shadcn/ui](https://ui.shadcn.com/). The app is set up as a chat interface that can answer questions about your data (see below)
- Your choice of 3 back-ends:
- **Next.js**: if you select this option, youll have a full-stack Next.js application that you can deploy to a host like [Vercel](https://vercel.com/) in just a few clicks. This uses [LlamaIndex.TS](https://www.npmjs.com/package/llamaindex), our TypeScript library.
- **Express**: if you want a more traditional Node.js application you can generate an Express backend. This also uses LlamaIndex.TS.
- **Python FastAPI**: if you select this option, youll get a backend powered by the [llama-index python package](https://pypi.org/project/llama-index/), which you can deploy to a service like Render or fly.io.
- The back-end has two endpoints (one streaming, the other one non-streaming) that allow you to send the state of your chat and receive additional responses
- You add arbitrary data sources to your chat, like local files, websites, or data retrieved from a database.
- Turn your chat into an AI agent by adding tools (functions called by the LLM).
- A set of pre-configured use cases to get you started, e.g. Agentic RAG, Data Analysis, Report Generation, etc.
- A front-end using components from [shadcn/ui](https://ui.shadcn.com/). The app is set up as a chat interface that can answer questions about your data or interact with your agent
- Your choice of two frameworks:
- **Next.js**: if you select this option, youll have a full-stack Next.js application that you can deploy to a host like [Vercel](https://vercel.com/) in just a few clicks. This uses [LlamaIndex.TS](https://www.npmjs.com/package/llamaindex), our TypeScript library with [LlamaIndex Server for TS](https://npmjs.com/package/@llamaindex/server).
- **Python FastAPI**: if you select this option, youll get full-stack Python application powered by the [llama-index Python package](https://pypi.org/project/llama-index/) and [LlamaIndex Server for Python](https://pypi.org/project/llama-index-server/)
- The app uses OpenAI by default, so you'll need an OpenAI API key, or you can customize it to use any of the dozens of LLMs we support.
Here's how it looks like:
https://github.com/user-attachments/assets/d57af1a1-d99b-4e9c-98d9-4cbd1327eff8
## Using your data
You can supply your own data; the app will index it and answer questions. Your generated app will have a folder called `data` (If you're using Express or Python and generate a frontend, it will be `./backend/data`).
Optionally, you can supply your own data; the app will index it and make use of it, e.g. to answer questions. Your generated app will have a folder called `data`.
The app will ingest any supported files you put in this directory. Your Next.js and Express apps use LlamaIndex.TS so they will be able to ingest any PDF, text, CSV, Markdown, Word and HTML files. The Python backend can read even more types, including video and audio files.
The app will ingest any supported files you put in this directory. Your Next.js apps use LlamaIndex.TS, so they will be able to ingest any PDF, text, CSV, Markdown, Word and HTML files. The Python backend can read even more types, including video and audio files.
Before you can use your data, you need to index it. If you're using the Next.js or Express apps, run:
Before you can use your data, you need to index it. If you're using the Next.js apps, run:
```bash
npm run generate
@@ -45,20 +52,16 @@ Then re-start your app. Remember you'll need to re-run `generate` if you add new
If you're using the Python backend, you can trigger indexing of your data by calling:
```bash
poetry run generate
uv run generate
```
## Want a front-end?
Optionally generate a frontend if you've selected the Python or Express back-ends. If you do so, `create-llama` will generate two folders: `frontend`, for your Next.js-based frontend code, and `backend` containing your API.
## Customizing the AI models
The app will default to OpenAI's `gpt-4-turbo` LLM and `text-embedding-3-large` embedding model.
The app will default to OpenAI's `gpt-4.1` LLM and `text-embedding-3-large` embedding model.
If you want to use different OpenAI models, add the `--ask-models` CLI parameter.
If you want to use different models, add the `--ask-models` CLI parameter.
You can also replace OpenAI with one of our [dozens of other supported LLMs](https://docs.llamaindex.ai/en/stable/module_guides/models/llms/modules.html).
You can also replace one of the default models with one of our [dozens of other supported LLMs](https://docs.llamaindex.ai/en/stable/module_guides/models/llms/modules.html).
To do so, you have to manually change the generated code (edit the `settings.ts` file for Typescript projects or the `settings.py` file for Python projects)
@@ -84,49 +87,31 @@ Need to install the following packages:
create-llama@latest
Ok to proceed? (y) y
✔ What is your project named? … my-app
✔ Which template would you like to use? Chat
✔ Which framework would you like to use? NextJS
Would you like to set up observability? No
✔ Please provide your OpenAI API key (leave blank to skip): …
✔ Which data source would you like to use? Use an example PDF
✔ Would you like to add another data source? No
✔ Would you like to use LlamaParse (improved parser for RAG - requires API key)? … no / yes
✔ Would you like to use a vector database? No, just store the data in the file system
✔ What use case do you want to build? Agentic RAG
✔ What language do you want to use? Python (FastAPI)
Do you want to use LlamaCloud services? No / Yes
✔ Please provide your LlamaCloud API key (leave blank to skip): …
? How would you like to proceed? - Use arrow-keys. Return to submit.
Just generate code (~1 sec)
Start in VSCode (~1 sec)
Generate code and install dependencies (~2 min)
Generate code, install dependencies, and run the app (~2 min)
Just generate code (~1 sec)
Start in VSCode (~1 sec)
Generate code and install dependencies (~2 min)
```
### Running non-interactively
You can also pass command line arguments to set up a new project
non-interactively. See `create-llama --help`:
```bash
create-llama <project-directory> [options]
Options:
-V, --version output the version number
--use-npm
Explicitly tell the CLI to bootstrap the app using npm
--use-pnpm
Explicitly tell the CLI to bootstrap the app using pnpm
--use-yarn
Explicitly tell the CLI to bootstrap the app using Yarn
```
non-interactively. For a list of the latest options, call `create-llama --help`.
## LlamaIndex Documentation
- [TS/JS docs](https://ts.llamaindex.ai/)
- [Python docs](https://docs.llamaindex.ai/en/stable/)
## LlamaIndex Server
The generated code is using the LlamaIndex Server, which serves LlamaIndex Workflows and Agent Workflows via an API server. See the following docs for more information:
- [LlamaIndex Server For TypeScript](./packages/server/README.md)
- [LlamaIndex Server For Python](./python/llama-index-server/README.md)
Inspired by and adapted from [create-next-app](https://github.com/vercel/next.js/tree/canary/packages/create-next-app)
-155
View File
@@ -1,155 +0,0 @@
/* eslint-disable import/no-extraneous-dependencies */
import path from "path";
import { green, yellow } from "picocolors";
import { tryGitInit } from "./helpers/git";
import { isFolderEmpty } from "./helpers/is-folder-empty";
import { getOnline } from "./helpers/is-online";
import { isWriteable } from "./helpers/is-writeable";
import { makeDir } from "./helpers/make-dir";
import fs from "fs";
import terminalLink from "terminal-link";
import type { InstallTemplateArgs } from "./helpers";
import { installTemplate } from "./helpers";
import { writeDevcontainer } from "./helpers/devcontainer";
import { templatesDir } from "./helpers/dir";
import { toolsRequireConfig } from "./helpers/tools";
export type InstallAppArgs = Omit<
InstallTemplateArgs,
"appName" | "root" | "isOnline" | "customApiPath"
> & {
appPath: string;
frontend: boolean;
};
export async function createApp({
template,
framework,
ui,
appPath,
packageManager,
frontend,
modelConfig,
llamaCloudKey,
communityProjectConfig,
llamapack,
vectorDb,
externalPort,
postInstallAction,
dataSources,
tools,
useLlamaParse,
observability,
}: InstallAppArgs): Promise<void> {
const root = path.resolve(appPath);
if (!(await isWriteable(path.dirname(root)))) {
console.error(
"The application path is not writable, please check folder permissions and try again.",
);
console.error(
"It is likely you do not have write permissions for this folder.",
);
process.exit(1);
}
const appName = path.basename(root);
await makeDir(root);
if (!isFolderEmpty(root, appName)) {
process.exit(1);
}
const useYarn = packageManager === "yarn";
const isOnline = !useYarn || (await getOnline());
console.log(`Creating a new LlamaIndex app in ${green(root)}.`);
console.log();
const args = {
appName,
root,
template,
framework,
ui,
packageManager,
isOnline,
modelConfig,
llamaCloudKey,
communityProjectConfig,
llamapack,
vectorDb,
externalPort,
postInstallAction,
dataSources,
tools,
useLlamaParse,
observability,
};
if (frontend) {
// install backend
const backendRoot = path.join(root, "backend");
await makeDir(backendRoot);
await installTemplate({ ...args, root: backendRoot, backend: true });
// install frontend
const frontendRoot = path.join(root, "frontend");
await makeDir(frontendRoot);
await installTemplate({
...args,
root: frontendRoot,
framework: "nextjs",
customApiPath: `http://localhost:${externalPort ?? 8000}/api/chat`,
backend: false,
});
// copy readme for fullstack
await fs.promises.copyFile(
path.join(templatesDir, "README-fullstack.md"),
path.join(root, "README.md"),
);
} else {
await installTemplate({ ...args, backend: true });
}
await writeDevcontainer(root, templatesDir, framework, frontend);
process.chdir(root);
if (tryGitInit(root)) {
console.log("Initialized a git repository.");
console.log();
}
if (toolsRequireConfig(tools)) {
const configFile =
framework === "fastapi" ? "config/tools.yaml" : "config/tools.json";
console.log(
yellow(
`You have selected tools that require configuration. Please configure them in the ${terminalLink(
configFile,
`file://${root}/${configFile}`,
)} file.`,
),
);
}
console.log("");
console.log(`${green("Success!")} Created ${appName} at ${appPath}`);
console.log(
`Now have a look at the ${terminalLink(
"README.md",
`file://${root}/README.md`,
)} and learn how to get started.`,
);
if (args.observability === "opentelemetry") {
console.log(
`\n${yellow("Observability")}: Visit the ${terminalLink(
"documentation",
"https://traceloop.com/docs/openllmetry/integrations",
)} to set up the environment variables and start seeing execution traces.`,
);
}
console.log();
}
-129
View File
@@ -1,129 +0,0 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { expect, test } from "@playwright/test";
import { ChildProcess } from "child_process";
import fs from "fs";
import path from "path";
import type {
TemplateFramework,
TemplatePostInstallAction,
TemplateType,
TemplateUI,
} from "../helpers";
import { createTestDir, runCreateLlama, type AppType } from "./utils";
const templateTypes: TemplateType[] = ["streaming"];
const templateFrameworks: TemplateFramework[] = [
"nextjs",
"express",
"fastapi",
];
const dataSources: string[] = ["--no-files", "--example-file"];
const templateUIs: TemplateUI[] = ["shadcn", "html"];
const templatePostInstallActions: TemplatePostInstallAction[] = [
"none",
"runApp",
];
for (const templateType of templateTypes) {
for (const templateFramework of templateFrameworks) {
for (const dataSource of dataSources) {
for (const templateUI of templateUIs) {
for (const templatePostInstallAction of templatePostInstallActions) {
const appType: AppType =
templateFramework === "nextjs" ? "" : "--frontend";
test.describe(`try create-llama ${templateType} ${templateFramework} ${dataSource} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
let port: number;
let externalPort: number;
let cwd: string;
let name: string;
let appProcess: ChildProcess;
// Only test without using vector db for now
const vectorDb = "none";
test.beforeAll(async () => {
port = Math.floor(Math.random() * 10000) + 10000;
externalPort = port + 1;
cwd = await createTestDir();
const result = await runCreateLlama(
cwd,
templateType,
templateFramework,
dataSource,
templateUI,
vectorDb,
appType,
port,
externalPort,
templatePostInstallAction,
);
name = result.projectName;
appProcess = result.appProcess;
});
test("App folder should exist", async () => {
const dirExists = fs.existsSync(path.join(cwd, name));
expect(dirExists).toBeTruthy();
});
test("Frontend should have a title", async ({ page }) => {
test.skip(templatePostInstallAction !== "runApp");
await page.goto(`http://localhost:${port}`);
await expect(page.getByText("Built by LlamaIndex")).toBeVisible();
});
test("Frontend should be able to submit a message and receive a response", async ({
page,
}) => {
test.skip(templatePostInstallAction !== "runApp");
await page.goto(`http://localhost:${port}`);
await page.fill("form input", "hello");
const [response] = await Promise.all([
page.waitForResponse(
(res) => {
return (
res.url().includes("/api/chat") && res.status() === 200
);
},
{
timeout: 1000 * 60,
},
),
page.click("form button[type=submit]"),
]);
const text = await response.text();
console.log("AI response when submitting message: ", text);
expect(response.ok()).toBeTruthy();
});
test("Backend frameworks should response when calling non-streaming chat API", async ({
request,
}) => {
test.skip(templatePostInstallAction !== "runApp");
test.skip(templateFramework === "nextjs");
const response = await request.post(
`http://localhost:${externalPort}/api/chat/request`,
{
data: {
messages: [
{
role: "user",
content: "Hello",
},
],
},
},
);
const text = await response.text();
console.log("AI response when calling API: ", text);
expect(response.ok()).toBeTruthy();
});
// clean processes
test.afterAll(async () => {
appProcess?.kill();
});
});
}
}
}
}
}
-168
View File
@@ -1,168 +0,0 @@
import { ChildProcess, exec } from "child_process";
import crypto from "node:crypto";
import { mkdir } from "node:fs/promises";
import * as path from "path";
import waitPort from "wait-port";
import {
TemplateFramework,
TemplatePostInstallAction,
TemplateType,
TemplateUI,
TemplateVectorDB,
} from "../helpers";
export type AppType = "--frontend" | "--no-frontend" | "";
export type CreateLlamaResult = {
projectName: string;
appProcess: ChildProcess;
};
// eslint-disable-next-line max-params
export async function checkAppHasStarted(
frontend: boolean,
framework: TemplateFramework,
port: number,
externalPort: number,
timeout: number,
) {
if (frontend) {
await Promise.all([
waitPort({
host: "localhost",
port: port,
timeout,
}),
waitPort({
host: "localhost",
port: externalPort,
timeout,
}),
]).catch((err) => {
console.error(err);
throw err;
});
} else {
let wPort: number;
if (framework === "nextjs") {
wPort = port;
} else {
wPort = externalPort;
}
await waitPort({
host: "localhost",
port: wPort,
timeout,
}).catch((err) => {
console.error(err);
throw err;
});
}
}
// eslint-disable-next-line max-params
export async function runCreateLlama(
cwd: string,
templateType: TemplateType,
templateFramework: TemplateFramework,
dataSource: string,
templateUI: TemplateUI,
vectorDb: TemplateVectorDB,
appType: AppType,
port: number,
externalPort: number,
postInstallAction: TemplatePostInstallAction,
): Promise<CreateLlamaResult> {
if (!process.env.OPENAI_API_KEY) {
throw new Error("Setting OPENAI_API_KEY is mandatory to run tests");
}
const name = [
templateType,
templateFramework,
dataSource,
templateUI,
appType,
].join("-");
const command = [
"create-llama",
name,
"--template",
templateType,
"--framework",
templateFramework,
dataSource,
"--ui",
templateUI,
"--vector-db",
vectorDb,
"--open-ai-key",
process.env.OPENAI_API_KEY,
appType,
"--use-pnpm",
"--port",
port,
"--external-port",
externalPort,
"--post-install-action",
postInstallAction,
"--tools",
"none",
"--no-llama-parse",
"--observability",
"none",
].join(" ");
console.log(`running command '${command}' in ${cwd}`);
const appProcess = exec(command, {
cwd,
env: {
...process.env,
},
});
appProcess.stderr?.on("data", (data) => {
console.log(data.toString());
});
appProcess.on("exit", (code) => {
if (code !== 0 && code !== null) {
throw new Error(`create-llama command was failed!`);
}
});
// Wait for app to start
if (postInstallAction === "runApp") {
await checkAppHasStarted(
appType === "--frontend",
templateFramework,
port,
externalPort,
1000 * 60 * 5,
);
} else {
// wait create-llama to exit
// we don't test install dependencies for now, so just set timeout for 10 seconds
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("create-llama timeout error"));
}, 1000 * 10);
appProcess.on("exit", (code) => {
if (code !== 0 && code !== null) {
clearTimeout(timeout);
reject(new Error("create-llama command was failed!"));
} else {
clearTimeout(timeout);
resolve(undefined);
}
});
});
}
return {
projectName: name,
appProcess,
};
}
export async function createTestDir() {
const cwd = path.join(__dirname, "cache", crypto.randomUUID());
await mkdir(cwd, { recursive: true });
return cwd;
}
+65
View File
@@ -0,0 +1,65 @@
import eslint from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
import globals from "globals";
import tseslint from "typescript-eslint";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
eslintConfigPrettier,
{
languageOptions: {
ecmaVersion: 2022,
sourceType: "module",
globals: {
...globals.browser,
...globals.node,
},
},
},
{
files: ["packages/create-llama/**"],
rules: {
"max-params": ["error", 4],
"prefer-const": "error",
"no-empty": "off",
"no-extra-boolean-cast": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-empty-object-type": "off",
"@typescript-eslint/no-wrapper-object-types": "off",
"@typescript-eslint/ban-ts-comment": "off",
},
},
{
files: ["packages/server/**"],
rules: {
"no-irregular-whitespace": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": [
"error",
{
ignoreRestArgs: true,
},
],
},
},
{
ignores: [
"python/**",
"**/*.mypy_cache/**",
"**/*.venv/**",
"**/*.ruff_cache/**",
"**/dist/**",
"**/e2e/cache/**",
"**/lib/*",
"**/.next/**",
"**/out/**",
"**/node_modules/**",
"**/build/**",
"packages/server/server/**",
"packages/server/project/**",
"packages/server/bin/**",
],
},
);
-6
View File
@@ -1,6 +0,0 @@
export const COMMUNITY_OWNER = "run-llama";
export const COMMUNITY_REPO = "create_llama_projects";
export const LLAMA_PACK_OWNER = "run-llama";
export const LLAMA_PACK_REPO = "llama_index";
export const LLAMA_PACK_FOLDER = "llama-index-packs";
export const LLAMA_PACK_FOLDER_PATH = `${LLAMA_PACK_OWNER}/${LLAMA_PACK_REPO}/main/${LLAMA_PACK_FOLDER}`;
-109
View File
@@ -1,109 +0,0 @@
import fs from "fs/promises";
import path from "path";
import yaml, { Document } from "yaml";
import { templatesDir } from "./dir";
import { DbSourceConfig, TemplateDataSource, WebSourceConfig } from "./types";
export const EXAMPLE_FILE: TemplateDataSource = {
type: "file",
config: {
path: path.join(templatesDir, "components", "data", "101.pdf"),
},
};
export function getDataSources(
files?: string,
exampleFile?: boolean,
): TemplateDataSource[] | undefined {
let dataSources: TemplateDataSource[] | undefined = undefined;
if (files) {
// If user specified files option, then the program should use context engine
dataSources = files.split(",").map((filePath) => ({
type: "file",
config: {
path: filePath,
},
}));
}
if (exampleFile) {
dataSources = [...(dataSources ? dataSources : []), EXAMPLE_FILE];
}
return dataSources;
}
export async function writeLoadersConfig(
root: string,
dataSources: TemplateDataSource[],
useLlamaParse?: boolean,
) {
if (dataSources.length === 0) return; // no datasources, no config needed
const loaderConfig = new Document({});
// Web loader config
if (dataSources.some((ds) => ds.type === "web")) {
const webLoaderConfig = new Document({});
// Create config for browser driver arguments
const driverArgNodeValue = webLoaderConfig.createNode([
"--no-sandbox",
"--disable-dev-shm-usage",
]);
driverArgNodeValue.commentBefore =
" The arguments to pass to the webdriver. E.g.: add --headless to run in headless mode";
webLoaderConfig.set("driver_arguments", driverArgNodeValue);
// Create config for urls
const urlConfigs = dataSources
.filter((ds) => ds.type === "web")
.map((ds) => {
const dsConfig = ds.config as WebSourceConfig;
return {
base_url: dsConfig.baseUrl,
prefix: dsConfig.prefix,
depth: dsConfig.depth,
};
});
const urlConfigNode = webLoaderConfig.createNode(urlConfigs);
urlConfigNode.commentBefore = ` base_url: The URL to start crawling with
prefix: Only crawl URLs matching the specified prefix
depth: The maximum depth for BFS traversal
You can add more websites by adding more entries (don't forget the - prefix from YAML)`;
webLoaderConfig.set("urls", urlConfigNode);
// Add web config to the loaders config
loaderConfig.set("web", webLoaderConfig);
}
// File loader config
if (dataSources.some((ds) => ds.type === "file")) {
// Add documentation to web loader config
const node = loaderConfig.createNode({
use_llama_parse: useLlamaParse,
});
node.commentBefore = ` use_llama_parse: Use LlamaParse if \`true\`. Needs a \`LLAMA_CLOUD_API_KEY\` from https://cloud.llamaindex.ai set as environment variable`;
loaderConfig.set("file", node);
}
// DB loader config
const dbLoaders = dataSources.filter((ds) => ds.type === "db");
if (dbLoaders.length > 0) {
const dbLoaderConfig = new Document({});
const configEntries = dbLoaders.map((ds) => {
const dsConfig = ds.config as DbSourceConfig;
return {
uri: dsConfig.uri,
queries: [dsConfig.queries],
};
});
const node = dbLoaderConfig.createNode(configEntries);
node.commentBefore = ` The configuration for the database loader, only supports MySQL and PostgreSQL databases for now.
uri: The URI for the database. E.g.: mysql+pymysql://user:password@localhost:3306/db or postgresql+psycopg2://user:password@localhost:5432/db
query: The query to fetch data from the database. E.g.: SELECT * FROM table`;
loaderConfig.set("db", node);
}
// Write loaders config
const loaderConfigPath = path.join(root, "config", "loaders.yaml");
await fs.mkdir(path.join(root, "config"), { recursive: true });
await fs.writeFile(loaderConfigPath, yaml.stringify(loaderConfig));
}
-187
View File
@@ -1,187 +0,0 @@
import { callPackageManager } from "./install";
import path from "path";
import { cyan } from "picocolors";
import fsExtra from "fs-extra";
import { writeLoadersConfig } from "./datasources";
import { createBackendEnvFile, createFrontendEnvFile } from "./env-variables";
import { PackageManager } from "./get-pkg-manager";
import { installLlamapackProject } from "./llama-pack";
import { isHavingPoetryLockFile, tryPoetryRun } from "./poetry";
import { installPythonTemplate } from "./python";
import { downloadAndExtractRepo } from "./repo";
import { ConfigFileType, writeToolsConfig } from "./tools";
import {
FileSourceConfig,
InstallTemplateArgs,
ModelConfig,
TemplateDataSource,
TemplateFramework,
TemplateVectorDB,
} from "./types";
import { installTSTemplate } from "./typescript";
// eslint-disable-next-line max-params
async function generateContextData(
framework: TemplateFramework,
modelConfig: ModelConfig,
packageManager?: PackageManager,
vectorDb?: TemplateVectorDB,
llamaCloudKey?: string,
useLlamaParse?: boolean,
) {
if (packageManager) {
const runGenerate = `${cyan(
framework === "fastapi"
? "poetry run generate"
: `${packageManager} run generate`,
)}`;
const modelConfigured = modelConfig.isConfigured();
const llamaCloudKeyConfigured = useLlamaParse
? llamaCloudKey || process.env["LLAMA_CLOUD_API_KEY"]
: true;
const hasVectorDb = vectorDb && vectorDb !== "none";
if (modelConfigured && llamaCloudKeyConfigured && !hasVectorDb) {
// If all the required environment variables are set, run the generate script
if (framework === "fastapi") {
if (isHavingPoetryLockFile()) {
console.log(`Running ${runGenerate} to generate the context data.`);
const result = tryPoetryRun("poetry run generate");
if (!result) {
console.log(`Failed to run ${runGenerate}.`);
process.exit(1);
}
console.log(`Generated context data`);
return;
}
} else {
console.log(`Running ${runGenerate} to generate the context data.`);
await callPackageManager(packageManager, true, ["run", "generate"]);
return;
}
}
// generate the message of what to do to run the generate script manually
const settings = [];
if (!modelConfigured) settings.push("your model provider API key");
if (!llamaCloudKeyConfigured) settings.push("your Llama Cloud key");
if (hasVectorDb) settings.push("your Vector DB environment variables");
const settingsMessage =
settings.length > 0 ? `After setting ${settings.join(" and ")}, ` : "";
const generateMessage = `run ${runGenerate} to generate the context data.`;
console.log(`\n${settingsMessage}${generateMessage}\n\n`);
}
}
const copyContextData = async (
root: string,
dataSources: TemplateDataSource[],
) => {
for (const dataSource of dataSources) {
const dataSourceConfig = dataSource?.config as FileSourceConfig;
// Copy local data
const dataPath = dataSourceConfig.path;
const destPath = path.join(root, "data", path.basename(dataPath));
console.log("Copying data from path:", dataPath);
await fsExtra.copy(dataPath, destPath);
}
};
const installCommunityProject = async ({
root,
communityProjectConfig,
}: Pick<InstallTemplateArgs, "root" | "communityProjectConfig">) => {
const { owner, repo, branch, filePath } = communityProjectConfig!;
console.log("\nInstalling community project:", filePath || repo);
await downloadAndExtractRepo(root, {
username: owner,
name: repo,
branch,
filePath: filePath || "",
});
};
export const installTemplate = async (
props: InstallTemplateArgs & { backend: boolean },
) => {
process.chdir(props.root);
if (props.template === "community" && props.communityProjectConfig) {
await installCommunityProject(props);
return;
}
if (props.template === "llamapack" && props.llamapack) {
await installLlamapackProject(props);
return;
}
if (props.framework === "fastapi") {
await installPythonTemplate(props);
// write loaders configuration (currently Python only)
await writeLoadersConfig(
props.root,
props.dataSources,
props.useLlamaParse,
);
} else {
await installTSTemplate(props);
}
// write tools configuration
await writeToolsConfig(
props.root,
props.tools,
props.framework === "fastapi" ? ConfigFileType.YAML : ConfigFileType.JSON,
);
if (props.backend) {
// This is a backend, so we need to copy the test data and create the env file.
// Copy the environment file to the target directory.
await createBackendEnvFile(props.root, {
modelConfig: props.modelConfig,
llamaCloudKey: props.llamaCloudKey,
vectorDb: props.vectorDb,
framework: props.framework,
dataSources: props.dataSources,
port: props.externalPort,
tools: props.tools,
});
if (props.dataSources.length > 0) {
console.log("\nGenerating context data...\n");
await copyContextData(
props.root,
props.dataSources.filter((ds) => ds.type === "file"),
);
if (
props.postInstallAction === "runApp" ||
props.postInstallAction === "dependencies"
) {
await generateContextData(
props.framework,
props.modelConfig,
props.packageManager,
props.vectorDb,
props.llamaCloudKey,
props.useLlamaParse,
);
}
}
// Create tool-output directory
if (props.tools && props.tools.length > 0) {
await fsExtra.mkdir(path.join(props.root, "tool-output"));
}
} else {
// this is a frontend for a full-stack app, create .env file with model information
await createFrontendEnvFile(props.root, {
customApiPath: props.customApiPath,
});
}
};
export * from "./types";
-148
View File
@@ -1,148 +0,0 @@
import fs from "fs/promises";
import got from "got";
import path from "path";
import { parse } from "smol-toml";
import {
LLAMA_PACK_FOLDER,
LLAMA_PACK_FOLDER_PATH,
LLAMA_PACK_OWNER,
LLAMA_PACK_REPO,
} from "./constant";
import { copy } from "./copy";
import { templatesDir } from "./dir";
import { addDependencies, installPythonDependencies } from "./python";
import { getRepoRawContent } from "./repo";
import { InstallTemplateArgs } from "./types";
const getLlamaPackFolderSHA = async () => {
const url = `https://api.github.com/repos/${LLAMA_PACK_OWNER}/${LLAMA_PACK_REPO}/contents`;
const response = await got(url, {
responseType: "json",
});
const data = response.body as any[];
const llamaPackFolder = data.find((item) => item.name === LLAMA_PACK_FOLDER);
return llamaPackFolder.sha;
};
const getLLamaPackFolderTree = async (
sha: string,
): Promise<
Array<{
path: string;
}>
> => {
const url = `https://api.github.com/repos/${LLAMA_PACK_OWNER}/${LLAMA_PACK_REPO}/git/trees/${sha}?recursive=1`;
const response = await got(url, {
responseType: "json",
});
return (response.body as any).tree;
};
export async function getAvailableLlamapackOptions(): Promise<
{
name: string;
folderPath: string;
}[]
> {
const EXAMPLE_RELATIVE_PATH = "/examples/example.py";
const PACK_FOLDER_SUBFIX = "llama-index-packs";
const llamaPackFolderSHA = await getLlamaPackFolderSHA();
const llamaPackTree = await getLLamaPackFolderTree(llamaPackFolderSHA);
// Return options that have example files
const exampleFiles = llamaPackTree.filter((item) =>
item.path.endsWith(EXAMPLE_RELATIVE_PATH),
);
const options = exampleFiles.map((file) => {
const packFolder = file.path.substring(
0,
file.path.indexOf(EXAMPLE_RELATIVE_PATH),
);
const packName = packFolder.substring(PACK_FOLDER_SUBFIX.length + 1);
return {
name: packName,
folderPath: packFolder,
};
});
return options;
}
const copyLlamapackEmptyProject = async ({
root,
}: Pick<InstallTemplateArgs, "root">) => {
const templatePath = path.join(
templatesDir,
"components/sample-projects/llamapack",
);
await copy("**", root, {
parents: true,
cwd: templatePath,
});
};
const copyData = async ({
root,
}: Pick<InstallTemplateArgs, "root" | "llamapack">) => {
const dataPath = path.join(templatesDir, "components/data");
await copy("**", path.join(root, "data"), {
parents: true,
cwd: dataPath,
});
};
const installLlamapackExample = async ({
root,
llamapack,
}: Pick<InstallTemplateArgs, "root" | "llamapack">) => {
const exampleFileName = "example.py";
const readmeFileName = "README.md";
const projectTomlFileName = "pyproject.toml";
const exampleFilePath = `${LLAMA_PACK_FOLDER_PATH}/${llamapack}/examples/${exampleFileName}`;
const readmeFilePath = `${LLAMA_PACK_FOLDER_PATH}/${llamapack}/${readmeFileName}`;
const projectTomlFilePath = `${LLAMA_PACK_FOLDER_PATH}/${llamapack}/${projectTomlFileName}`;
// Download example.py from llamapack and save to root
const exampleContent = await getRepoRawContent(exampleFilePath);
await fs.writeFile(path.join(root, exampleFileName), exampleContent);
// Download README.md from llamapack and combine with README-template.md,
// save to root and then delete template file
const readmeContent = await getRepoRawContent(readmeFilePath);
const readmeTemplateContent = await fs.readFile(
path.join(root, "README-template.md"),
"utf-8",
);
await fs.writeFile(
path.join(root, readmeFileName),
`${readmeContent}\n${readmeTemplateContent}`,
);
await fs.unlink(path.join(root, "README-template.md"));
// Download pyproject.toml from llamapack, parse it to get package name and version,
// then add it as a dependency to current toml file in the project
const projectTomlContent = await getRepoRawContent(projectTomlFilePath);
const fileParsed = parse(projectTomlContent) as any;
const packageName = fileParsed.tool.poetry.name;
const packageVersion = fileParsed.tool.poetry.version;
await addDependencies(root, [
{
name: packageName,
version: packageVersion,
},
]);
};
export const installLlamapackProject = async ({
root,
llamapack,
postInstallAction,
}: Pick<InstallTemplateArgs, "root" | "llamapack" | "postInstallAction">) => {
console.log("\nInstalling Llamapack project:", llamapack!);
await copyLlamapackEmptyProject({ root });
await copyData({ root });
await installLlamapackExample({ root, llamapack });
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
installPythonDependencies({ noRoot: true });
}
};
-36
View File
@@ -1,36 +0,0 @@
/* eslint-disable import/no-extraneous-dependencies */
import { execSync } from "child_process";
import fs from "fs";
export function isPoetryAvailable(): boolean {
try {
execSync("poetry --version", { stdio: "ignore" });
return true;
} catch (_) {}
return false;
}
export function tryPoetryInstall(noRoot: boolean): boolean {
try {
execSync(`poetry install${noRoot ? " --no-root" : ""}`, {
stdio: "inherit",
});
return true;
} catch (_) {}
return false;
}
export function tryPoetryRun(command: string): boolean {
try {
execSync(`poetry run ${command}`, { stdio: "inherit" });
return true;
} catch (_) {}
return false;
}
export function isHavingPoetryLockFile(): boolean {
try {
return fs.existsSync("poetry.lock");
} catch (_) {}
return false;
}
-87
View File
@@ -1,87 +0,0 @@
import ciInfo from "ci-info";
import prompts from "prompts";
import { ModelConfigParams } from ".";
import { questionHandlers, toChoice } from "../../questions";
const MODELS = ["gemini-1.5-pro-latest", "gemini-pro", "gemini-pro-vision"];
type ModelData = {
dimensions: number;
};
const EMBEDDING_MODELS: Record<string, ModelData> = {
"embedding-001": { dimensions: 768 },
"text-embedding-004": { dimensions: 768 },
};
const DEFAULT_MODEL = MODELS[0];
const DEFAULT_EMBEDDING_MODEL = Object.keys(EMBEDDING_MODELS)[0];
const DEFAULT_DIMENSIONS = Object.values(EMBEDDING_MODELS)[0].dimensions;
type GeminiQuestionsParams = {
apiKey?: string;
askModels: boolean;
};
export async function askGeminiQuestions({
askModels,
apiKey,
}: GeminiQuestionsParams): Promise<ModelConfigParams> {
const config: ModelConfigParams = {
apiKey,
model: DEFAULT_MODEL,
embeddingModel: DEFAULT_EMBEDDING_MODEL,
dimensions: DEFAULT_DIMENSIONS,
isConfigured(): boolean {
if (config.apiKey) {
return true;
}
if (process.env["GOOGLE_API_KEY"]) {
return true;
}
return false;
},
};
if (!config.apiKey) {
const { key } = await prompts(
{
type: "text",
name: "key",
message:
"Please provide your Google API key (or leave blank to use GOOGLE_API_KEY env variable):",
},
questionHandlers,
);
config.apiKey = key || process.env.GOOGLE_API_KEY;
}
// use default model values in CI or if user should not be asked
const useDefaults = ciInfo.isCI || !askModels;
if (!useDefaults) {
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which LLM model would you like to use?",
choices: MODELS.map(toChoice),
initial: 0,
},
questionHandlers,
);
config.model = model;
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: Object.keys(EMBEDDING_MODELS).map(toChoice),
initial: 0,
},
questionHandlers,
);
config.embeddingModel = embeddingModel;
config.dimensions = EMBEDDING_MODELS[embeddingModel].dimensions;
}
return config;
}
-67
View File
@@ -1,67 +0,0 @@
import ciInfo from "ci-info";
import prompts from "prompts";
import { questionHandlers } from "../../questions";
import { ModelConfig, ModelProvider } from "../types";
import { askAnthropicQuestions } from "./anthropic";
import { askGeminiQuestions } from "./gemini";
import { askOllamaQuestions } from "./ollama";
import { askOpenAIQuestions } from "./openai";
const DEFAULT_MODEL_PROVIDER = "openai";
export type ModelConfigQuestionsParams = {
openAiKey?: string;
askModels: boolean;
};
export type ModelConfigParams = Omit<ModelConfig, "provider">;
export async function askModelConfig({
askModels,
openAiKey,
}: ModelConfigQuestionsParams): Promise<ModelConfig> {
let modelProvider: ModelProvider = DEFAULT_MODEL_PROVIDER;
if (askModels && !ciInfo.isCI) {
const { provider } = await prompts(
{
type: "select",
name: "provider",
message: "Which model provider would you like to use",
choices: [
{
title: "OpenAI",
value: "openai",
},
{ title: "Ollama", value: "ollama" },
{ title: "Anthropic", value: "anthropic" },
{ title: "Gemini", value: "gemini" },
],
initial: 0,
},
questionHandlers,
);
modelProvider = provider;
}
let modelConfig: ModelConfigParams;
switch (modelProvider) {
case "ollama":
modelConfig = await askOllamaQuestions({ askModels });
break;
case "anthropic":
modelConfig = await askAnthropicQuestions({ askModels });
break;
case "gemini":
modelConfig = await askGeminiQuestions({ askModels });
break;
default:
modelConfig = await askOpenAIQuestions({
openAiKey,
askModels,
});
}
return {
...modelConfig,
provider: modelProvider,
};
}
-358
View File
@@ -1,358 +0,0 @@
import fs from "fs/promises";
import path from "path";
import { cyan, red } from "picocolors";
import { parse, stringify } from "smol-toml";
import terminalLink from "terminal-link";
import { assetRelocator, copy } from "./copy";
import { templatesDir } from "./dir";
import { isPoetryAvailable, tryPoetryInstall } from "./poetry";
import { Tool } from "./tools";
import {
InstallTemplateArgs,
ModelConfig,
TemplateDataSource,
TemplateVectorDB,
} from "./types";
interface Dependency {
name: string;
version?: string;
extras?: string[];
}
const getAdditionalDependencies = (
modelConfig: ModelConfig,
vectorDb?: TemplateVectorDB,
dataSources?: TemplateDataSource[],
tools?: Tool[],
) => {
const dependencies: Dependency[] = [];
// Add vector db dependencies
switch (vectorDb) {
case "mongo": {
dependencies.push({
name: "llama-index-vector-stores-mongodb",
version: "^0.1.3",
});
break;
}
case "pg": {
dependencies.push({
name: "llama-index-vector-stores-postgres",
version: "^0.1.1",
});
break;
}
case "pinecone": {
dependencies.push({
name: "llama-index-vector-stores-pinecone",
version: "^0.1.3",
});
break;
}
case "milvus": {
dependencies.push({
name: "llama-index-vector-stores-milvus",
version: "^0.1.6",
});
dependencies.push({
name: "pymilvus",
version: "2.3.7",
});
break;
}
case "astra": {
dependencies.push({
name: "llama-index-vector-stores-astra-db",
version: "^0.1.5",
});
break;
}
case "qdrant": {
dependencies.push({
name: "llama-index-vector-stores-qdrant",
version: "^0.2.8",
});
break;
}
case "chroma": {
dependencies.push({
name: "llama-index-vector-stores-chroma",
version: "^0.1.8",
});
break;
}
}
// Add data source dependencies
if (dataSources) {
for (const ds of dataSources) {
const dsType = ds?.type;
switch (dsType) {
case "file":
dependencies.push({
name: "docx2txt",
version: "^0.8",
});
break;
case "web":
dependencies.push({
name: "llama-index-readers-web",
version: "^0.1.6",
});
break;
case "db":
dependencies.push({
name: "llama-index-readers-database",
version: "^0.1.3",
});
dependencies.push({
name: "pymysql",
version: "^1.1.0",
extras: ["rsa"],
});
dependencies.push({
name: "psycopg2",
version: "^2.9.9",
});
break;
}
}
}
// Add tools dependencies
console.log("Adding tools dependencies");
tools?.forEach((tool) => {
tool.dependencies?.forEach((dep) => {
dependencies.push(dep);
});
});
switch (modelConfig.provider) {
case "ollama":
dependencies.push({
name: "llama-index-llms-ollama",
version: "0.1.2",
});
dependencies.push({
name: "llama-index-embeddings-ollama",
version: "0.1.2",
});
break;
case "openai":
dependencies.push({
name: "llama-index-agent-openai",
version: "0.2.2",
});
break;
case "anthropic":
dependencies.push({
name: "llama-index-llms-anthropic",
version: "0.1.10",
});
dependencies.push({
name: "llama-index-embeddings-huggingface",
version: "0.2.0",
});
break;
case "gemini":
dependencies.push({
name: "llama-index-llms-gemini",
version: "0.1.7",
});
dependencies.push({
name: "llama-index-embeddings-gemini",
version: "0.1.6",
});
break;
}
return dependencies;
};
const mergePoetryDependencies = (
dependencies: Dependency[],
existingDependencies: Record<string, Omit<Dependency, "name">>,
) => {
for (const dependency of dependencies) {
let value = existingDependencies[dependency.name] ?? {};
// default string value is equal to attribute "version"
if (typeof value === "string") {
value = { version: value };
}
value.version = dependency.version ?? value.version;
value.extras = dependency.extras ?? value.extras;
if (value.version === undefined) {
throw new Error(
`Dependency "${dependency.name}" is missing attribute "version"!`,
);
}
existingDependencies[dependency.name] = value;
}
};
export const addDependencies = async (
projectDir: string,
dependencies: Dependency[],
) => {
if (dependencies.length === 0) return;
const FILENAME = "pyproject.toml";
try {
// Parse toml file
const file = path.join(projectDir, FILENAME);
const fileContent = await fs.readFile(file, "utf8");
const fileParsed = parse(fileContent);
// Modify toml dependencies
const tool = fileParsed.tool as any;
const existingDependencies = tool.poetry.dependencies;
mergePoetryDependencies(dependencies, existingDependencies);
// Write toml file
const newFileContent = stringify(fileParsed);
await fs.writeFile(file, newFileContent);
const dependenciesString = dependencies.map((d) => d.name).join(", ");
console.log(`\nAdded ${dependenciesString} to ${cyan(FILENAME)}\n`);
} catch (error) {
console.log(
`Error while updating dependencies for Poetry project file ${FILENAME}\n`,
error,
);
}
};
export const installPythonDependencies = (
{ noRoot }: { noRoot: boolean } = { noRoot: false },
) => {
if (isPoetryAvailable()) {
console.log(
`Installing python dependencies using poetry. This may take a while...`,
);
const installSuccessful = tryPoetryInstall(noRoot);
if (!installSuccessful) {
console.error(
red(
"Installing dependencies using poetry failed. Please check error log above and try running create-llama again.",
),
);
process.exit(1);
}
} else {
console.error(
red(
`Poetry is not available in the current environment. Please check ${terminalLink(
"Poetry Installation",
`https://python-poetry.org/docs/#installation`,
)} to install poetry first, then run create-llama again.`,
),
);
process.exit(1);
}
};
export const installPythonTemplate = async ({
root,
template,
framework,
vectorDb,
dataSources,
tools,
postInstallAction,
observability,
modelConfig,
}: Pick<
InstallTemplateArgs,
| "root"
| "framework"
| "template"
| "vectorDb"
| "dataSources"
| "tools"
| "postInstallAction"
| "observability"
| "modelConfig"
>) => {
console.log("\nInitializing Python project with template:", template, "\n");
const templatePath = path.join(templatesDir, "types", template, framework);
await copy("**", root, {
parents: true,
cwd: templatePath,
rename: assetRelocator,
});
const compPath = path.join(templatesDir, "components");
const enginePath = path.join(root, "app", "engine");
// Copy selected vector DB
await copy("**", enginePath, {
parents: true,
cwd: path.join(compPath, "vectordbs", "python", vectorDb ?? "none"),
});
// Copy all loaders to enginePath
const loaderPath = path.join(enginePath, "loaders");
await copy("**", loaderPath, {
parents: true,
cwd: path.join(compPath, "loaders", "python"),
});
// Select and copy engine code based on data sources and tools
let engine;
tools = tools ?? [];
if (dataSources.length > 0 && tools.length === 0) {
console.log("\nNo tools selected - use optimized context chat engine\n");
engine = "chat";
} else {
engine = "agent";
}
await copy("**", enginePath, {
parents: true,
cwd: path.join(compPath, "engines", "python", engine),
});
console.log("Adding additional dependencies");
const addOnDependencies = getAdditionalDependencies(
modelConfig,
vectorDb,
dataSources,
tools,
);
if (observability === "opentelemetry") {
addOnDependencies.push({
name: "traceloop-sdk",
version: "^0.15.11",
});
const templateObservabilityPath = path.join(
templatesDir,
"components",
"observability",
"python",
"opentelemetry",
);
await copy("**", path.join(root, "app"), {
cwd: templateObservabilityPath,
});
}
await addDependencies(root, addOnDependencies);
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
installPythonDependencies();
}
// Copy deployment files for python
await copy("**", root, {
cwd: path.join(compPath, "deployments", "python"),
});
};
-134
View File
@@ -1,134 +0,0 @@
import { createWriteStream, promises } from "fs";
import got from "got";
import { tmpdir } from "os";
import { join } from "path";
import { Stream } from "stream";
import tar from "tar";
import { promisify } from "util";
import { makeDir } from "./make-dir";
import { CommunityProjectConfig } from "./types";
export type RepoInfo = {
username: string;
name: string;
branch: string;
filePath: string;
};
const pipeline = promisify(Stream.pipeline);
async function downloadTar(url: string) {
const tempFile = join(tmpdir(), `next.js-cna-example.temp-${Date.now()}`);
await pipeline(got.stream(url), createWriteStream(tempFile));
return tempFile;
}
export async function downloadAndExtractRepo(
root: string,
{ username, name, branch, filePath }: RepoInfo,
) {
await makeDir(root);
const tempFile = await downloadTar(
`https://codeload.github.com/${username}/${name}/tar.gz/${branch}`,
);
await tar.x({
file: tempFile,
cwd: root,
strip: filePath ? filePath.split("/").length + 1 : 1,
filter: (p) =>
p.startsWith(
`${name}-${branch.replace(/\//g, "-")}${
filePath ? `/${filePath}/` : "/"
}`,
),
});
await promises.unlink(tempFile);
}
const getRepoInfo = async (owner: string, repo: string) => {
const repoInfoRes = await got(
`https://api.github.com/repos/${owner}/${repo}`,
{
responseType: "json",
},
);
const data = repoInfoRes.body as any;
return data;
};
export async function getProjectOptions(
owner: string,
repo: string,
): Promise<
{
value: CommunityProjectConfig;
title: string;
}[]
> {
// TODO: consider using octokit (https://github.com/octokit) if more changes are needed in the future
const getCommunityProjectConfig = async (
item: any,
): Promise<CommunityProjectConfig | null> => {
// if item is a folder, return the path with default owner, repo, and main branch
if (item.type === "dir")
return {
owner,
repo,
branch: "main",
filePath: item.path,
};
// check if it's a submodule (has size = 0 and different owner & repo)
if (item.type === "file") {
if (item.size !== 0) return null; // submodules have size = 0
// get owner and repo from git_url
const { git_url } = item;
const startIndex = git_url.indexOf("repos/") + 6;
const endIndex = git_url.indexOf("/git");
const ownerRepoStr = git_url.substring(startIndex, endIndex);
const [owner, repo] = ownerRepoStr.split("/");
// quick fetch repo info to get the default branch
const { default_branch } = await getRepoInfo(owner, repo);
// return the path with default owner, repo, and main branch (path is empty for submodules)
return {
owner,
repo,
branch: default_branch,
};
}
return null;
};
const url = `https://api.github.com/repos/${owner}/${repo}/contents`;
const response = await got(url, {
responseType: "json",
});
const data = response.body as any[];
const projectConfigs: CommunityProjectConfig[] = [];
for (const item of data) {
const communityProjectConfig = await getCommunityProjectConfig(item);
if (communityProjectConfig) projectConfigs.push(communityProjectConfig);
}
return projectConfigs.map((config) => {
return {
value: config,
title: config.filePath || config.repo, // for submodules, use repo name as title
};
});
}
export async function getRepoRawContent(repoFilePath: string) {
const url = `https://raw.githubusercontent.com/${repoFilePath}`;
const response = await got(url, {
responseType: "text",
});
return response.body;
}
-88
View File
@@ -1,88 +0,0 @@
import { ChildProcess, SpawnOptions, spawn } from "child_process";
import path from "path";
import { TemplateFramework } from "./types";
const createProcess = (
command: string,
args: string[],
options: SpawnOptions,
) => {
return spawn(command, args, {
...options,
shell: true,
})
.on("exit", function (code) {
if (code !== 0) {
console.log(`Child process exited with code=${code}`);
process.exit(1);
}
})
.on("error", function (err) {
console.log("Error when running chill process: ", err);
process.exit(1);
});
};
// eslint-disable-next-line max-params
export async function runApp(
appPath: string,
frontend: boolean,
framework: TemplateFramework,
port?: number,
externalPort?: number,
): Promise<any> {
let backendAppProcess: ChildProcess;
let frontendAppProcess: ChildProcess | undefined;
const frontendPort = port || 3000;
let backendPort = externalPort || 8000;
// Callback to kill app processes
process.on("exit", () => {
console.log("Killing app processes...");
backendAppProcess.kill();
frontendAppProcess?.kill();
});
let backendCommand = "";
let backendArgs: string[];
if (framework === "fastapi") {
backendCommand = "poetry";
backendArgs = [
"run",
"uvicorn",
"main:app",
"--host=0.0.0.0",
"--port=" + backendPort,
];
} else if (framework === "nextjs") {
backendCommand = "npm";
backendArgs = ["run", "dev"];
backendPort = frontendPort;
} else {
backendCommand = "npm";
backendArgs = ["run", "dev"];
}
if (frontend) {
return new Promise((resolve, reject) => {
backendAppProcess = createProcess(backendCommand, backendArgs, {
stdio: "inherit",
cwd: path.join(appPath, "backend"),
env: { ...process.env, PORT: `${backendPort}` },
});
frontendAppProcess = createProcess("npm", ["run", "dev"], {
stdio: "inherit",
cwd: path.join(appPath, "frontend"),
env: { ...process.env, PORT: `${frontendPort}` },
});
});
} else {
return new Promise((resolve, reject) => {
backendAppProcess = createProcess(backendCommand, backendArgs, {
stdio: "inherit",
cwd: path.join(appPath),
env: { ...process.env, PORT: `${backendPort}` },
});
});
}
}
-190
View File
@@ -1,190 +0,0 @@
import fs from "fs/promises";
import path from "path";
import { red } from "picocolors";
import yaml from "yaml";
import { EnvVar } from "./env-variables";
import { makeDir } from "./make-dir";
import { TemplateFramework } from "./types";
export const TOOL_SYSTEM_PROMPT_ENV_VAR = "TOOL_SYSTEM_PROMPT";
export enum ToolType {
LLAMAHUB = "llamahub",
LOCAL = "local",
}
export type Tool = {
display: string;
name: string;
config?: Record<string, any>;
dependencies?: ToolDependencies[];
supportedFrameworks?: Array<TemplateFramework>;
type: ToolType;
envVars?: EnvVar[];
};
export type ToolDependencies = {
name: string;
version?: string;
};
export const supportedTools: Tool[] = [
{
display: "Google Search (configuration required after installation)",
name: "google.GoogleSearchToolSpec",
config: {
engine:
"Your search engine id, see https://developers.google.com/custom-search/v1/overview#prerequisites",
key: "Your search api key",
num: 2,
},
dependencies: [
{
name: "llama-index-tools-google",
version: "0.1.2",
},
],
supportedFrameworks: ["fastapi"],
type: ToolType.LLAMAHUB,
envVars: [
{
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
description: "System prompt for google search tool.",
value: `You are a Google search agent. You help users to get information from Google search.`,
},
],
},
{
display: "Wikipedia",
name: "wikipedia.WikipediaToolSpec",
dependencies: [
{
name: "llama-index-tools-wikipedia",
version: "0.1.2",
},
],
supportedFrameworks: ["fastapi", "express", "nextjs"],
type: ToolType.LLAMAHUB,
envVars: [
{
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
description: "System prompt for wiki tool.",
value: `You are a Wikipedia agent. You help users to get information from Wikipedia.`,
},
],
},
{
display: "Weather",
name: "weather",
dependencies: [],
supportedFrameworks: ["fastapi", "express", "nextjs"],
type: ToolType.LOCAL,
envVars: [
{
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
description: "System prompt for weather tool.",
value: `You are a weather forecast agent. You help users to get the weather forecast for a given location.`,
},
],
},
{
display: "Code Interpreter",
name: "interpreter",
dependencies: [
{
name: "e2b_code_interpreter",
version: "0.0.7",
},
],
supportedFrameworks: ["fastapi", "express", "nextjs"],
type: ToolType.LOCAL,
envVars: [
{
name: "E2B_API_KEY",
description:
"E2B_API_KEY key is required to run code interpreter tool. Get it here: https://e2b.dev/docs/getting-started/api-key",
},
{
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
description: "System prompt for code interpreter tool.",
value: `You are a Python interpreter.
- You are given tasks to complete and you run python code to solve them.
- The python code runs in a Jupyter notebook. Every time you call \`interpreter\` tool, the python code is executed in a separate cell. It's okay to make multiple calls to \`interpreter\`.
- Display visualizations using matplotlib or any other visualization library directly in the notebook. Shouldn't save the visualizations to a file, just return the base64 encoded data.
- You can install any pip package (if it exists) if you need to but the usual packages for data analysis are already preinstalled.
- You can run any python code you want in a secure environment.
- Use absolute url from result to display images or any other media.`,
},
],
},
];
export const getTool = (toolName: string): Tool | undefined => {
return supportedTools.find((tool) => tool.name === toolName);
};
export const getTools = (toolsName: string[]): Tool[] => {
const tools: Tool[] = [];
for (const toolName of toolsName) {
const tool = getTool(toolName);
if (!tool) {
console.log(
red(
`Error: Tool '${toolName}' is not supported. Supported tools are: ${supportedTools
.map((t) => t.name)
.join(", ")}`,
),
);
process.exit(1);
}
tools.push(tool);
}
return tools;
};
export const toolsRequireConfig = (tools?: Tool[]): boolean => {
if (tools) {
return tools?.some((tool) => Object.keys(tool.config || {}).length > 0);
}
return false;
};
export enum ConfigFileType {
YAML = "yaml",
JSON = "json",
}
export const writeToolsConfig = async (
root: string,
tools: Tool[] = [],
type: ConfigFileType = ConfigFileType.YAML,
) => {
if (tools.length === 0) return; // no tools selected, no config need
const configContent: {
[key in ToolType]: Record<string, any>;
} = {
local: {},
llamahub: {},
};
tools.forEach((tool) => {
if (tool.type === ToolType.LLAMAHUB) {
configContent.llamahub[tool.name] = tool.config ?? {};
}
if (tool.type === ToolType.LOCAL) {
configContent.local[tool.name] = tool.config ?? {};
}
});
const configPath = path.join(root, "config");
await makeDir(configPath);
if (type === ConfigFileType.YAML) {
await fs.writeFile(
path.join(configPath, "tools.yaml"),
yaml.stringify(configContent),
);
} else {
await fs.writeFile(
path.join(configPath, "tools.json"),
JSON.stringify(configContent, null, 2),
);
}
};
-278
View File
@@ -1,278 +0,0 @@
import fs from "fs/promises";
import os from "os";
import path from "path";
import { bold, cyan } from "picocolors";
import { assetRelocator, copy } from "../helpers/copy";
import { callPackageManager } from "../helpers/install";
import { templatesDir } from "./dir";
import { PackageManager } from "./get-pkg-manager";
import { InstallTemplateArgs } from "./types";
/**
* Install a LlamaIndex internal template to a given `root` directory.
*/
export const installTSTemplate = async ({
appName,
root,
packageManager,
isOnline,
template,
framework,
ui,
vectorDb,
postInstallAction,
backend,
observability,
tools,
dataSources,
useLlamaParse,
}: InstallTemplateArgs & { backend: boolean }) => {
console.log(bold(`Using ${packageManager}.`));
/**
* Copy the template files to the target directory.
*/
console.log("\nInitializing project with template:", template, "\n");
const templatePath = path.join(templatesDir, "types", template, framework);
const copySource = ["**"];
await copy(copySource, root, {
parents: true,
cwd: templatePath,
rename: assetRelocator,
});
/**
* If next.js is used, update its configuration if necessary
*/
if (framework === "nextjs") {
const nextConfigJsonFile = path.join(root, "next.config.json");
const nextConfigJson: any = JSON.parse(
await fs.readFile(nextConfigJsonFile, "utf8"),
);
if (!backend) {
// update next.config.json for static site generation
nextConfigJson.output = "export";
nextConfigJson.images = { unoptimized: true };
console.log("\nUsing static site generation\n");
} else {
if (vectorDb === "milvus") {
nextConfigJson.experimental.serverComponentsExternalPackages =
nextConfigJson.experimental.serverComponentsExternalPackages ?? [];
nextConfigJson.experimental.serverComponentsExternalPackages.push(
"@zilliz/milvus2-sdk-node",
);
}
}
await fs.writeFile(
nextConfigJsonFile,
JSON.stringify(nextConfigJson, null, 2) + os.EOL,
);
const webpackConfigOtelFile = path.join(root, "webpack.config.o11y.mjs");
if (observability === "opentelemetry") {
const webpackConfigDefaultFile = path.join(root, "webpack.config.mjs");
await fs.rm(webpackConfigDefaultFile);
await fs.rename(webpackConfigOtelFile, webpackConfigDefaultFile);
} else {
await fs.rm(webpackConfigOtelFile);
}
}
// copy observability component
if (observability && observability !== "none") {
const chosenObservabilityPath = path.join(
templatesDir,
"components",
"observability",
"typescript",
observability,
);
const relativeObservabilityPath = framework === "nextjs" ? "app" : "src";
await copy(
"**",
path.join(root, relativeObservabilityPath, "observability"),
{ cwd: chosenObservabilityPath },
);
}
const compPath = path.join(templatesDir, "components");
const relativeEngineDestPath =
framework === "nextjs"
? path.join("app", "api", "chat")
: path.join("src", "controllers");
const enginePath = path.join(root, relativeEngineDestPath, "engine");
// copy vector db component
console.log("\nUsing vector DB:", vectorDb ?? "none", "\n");
await copy("**", enginePath, {
parents: true,
cwd: path.join(compPath, "vectordbs", "typescript", vectorDb ?? "none"),
});
// copy loader component (TS only supports llama_parse and file for now)
const loaderFolder = useLlamaParse ? "llama_parse" : "file";
await copy("**", enginePath, {
parents: true,
cwd: path.join(compPath, "loaders", "typescript", loaderFolder),
});
// Select and copy engine code based on data sources and tools
let engine;
tools = tools ?? [];
if (dataSources.length > 0 && tools.length === 0) {
console.log("\nNo tools selected - use optimized context chat engine\n");
engine = "chat";
} else {
engine = "agent";
}
await copy("**", enginePath, {
parents: true,
cwd: path.join(compPath, "engines", "typescript", engine),
});
/**
* Copy the selected UI files to the target directory and reference it.
*/
if (framework === "nextjs" && ui !== "shadcn") {
console.log("\nUsing UI:", ui, "\n");
const uiPath = path.join(compPath, "ui", ui);
const destUiPath = path.join(root, "app", "components", "ui");
// remove the default ui folder
await fs.rm(destUiPath, { recursive: true });
// copy the selected ui folder
await copy("**", destUiPath, {
parents: true,
cwd: uiPath,
rename: assetRelocator,
});
}
/** Modify frontend code to use custom API path */
if (framework === "nextjs" && !backend) {
console.log(
"\nUsing external API for frontend, removing API code and configuration\n",
);
// remove the default api folder and config folder
await fs.rm(path.join(root, "app", "api"), { recursive: true });
await fs.rm(path.join(root, "config"), { recursive: true, force: true });
}
const packageJson = await updatePackageJson({
root,
appName,
dataSources,
relativeEngineDestPath,
framework,
ui,
observability,
});
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
await installTSDependencies(packageJson, packageManager, isOnline);
}
// Copy deployment files for typescript
await copy("**", root, {
cwd: path.join(compPath, "deployments", "typescript"),
});
};
async function updatePackageJson({
root,
appName,
dataSources,
relativeEngineDestPath,
framework,
ui,
observability,
}: Pick<
InstallTemplateArgs,
"root" | "appName" | "dataSources" | "framework" | "ui" | "observability"
> & {
relativeEngineDestPath: string;
}): Promise<any> {
const packageJsonFile = path.join(root, "package.json");
const packageJson: any = JSON.parse(
await fs.readFile(packageJsonFile, "utf8"),
);
packageJson.name = appName;
packageJson.version = "0.1.0";
if (relativeEngineDestPath) {
// TODO: move script to {root}/scripts for all frameworks
// add generate script if using context engine
packageJson.scripts = {
...packageJson.scripts,
generate: `tsx ${path.join(
relativeEngineDestPath,
"engine",
"generate.ts",
)}`,
};
}
if (framework === "nextjs" && ui === "html") {
// remove shadcn dependencies if html ui is selected
packageJson.dependencies = {
...packageJson.dependencies,
"tailwind-merge": undefined,
"@radix-ui/react-slot": undefined,
"class-variance-authority": undefined,
clsx: undefined,
"lucide-react": undefined,
remark: undefined,
"remark-code-import": undefined,
"remark-gfm": undefined,
"remark-math": undefined,
"react-markdown": undefined,
"react-syntax-highlighter": undefined,
};
packageJson.devDependencies = {
...packageJson.devDependencies,
"@types/react-syntax-highlighter": undefined,
};
}
if (observability === "opentelemetry") {
packageJson.dependencies = {
...packageJson.dependencies,
"@traceloop/node-server-sdk": "^0.5.19",
};
packageJson.devDependencies = {
...packageJson.devDependencies,
"node-loader": "^2.0.0",
};
}
await fs.writeFile(
packageJsonFile,
JSON.stringify(packageJson, null, 2) + os.EOL,
);
return packageJson;
}
async function installTSDependencies(
packageJson: any,
packageManager: PackageManager,
isOnline: boolean,
): Promise<void> {
console.log("\nInstalling dependencies:");
for (const dependency in packageJson.dependencies)
console.log(`- ${cyan(dependency)}`);
console.log("\nInstalling devDependencies:");
for (const dependency in packageJson.devDependencies)
console.log(`- ${cyan(dependency)}`);
console.log();
await callPackageManager(packageManager, isOnline).catch((error) => {
console.error("Failed to install TS dependencies. Exiting...");
process.exit(1);
});
}
+35 -62
View File
@@ -1,82 +1,55 @@
{
"name": "create-llama",
"version": "0.1.8",
"description": "Create LlamaIndex-powered apps with one command",
"name": "create-llama-monorepo",
"version": "1.0.0",
"private": true,
"description": "Monorepo for create-llama",
"keywords": [
"rag",
"llamaindex",
"next.js"
"llamaindex"
],
"repository": {
"type": "git",
"url": "https://github.com/run-llama/LlamaIndexTS",
"directory": "packages/create-llama"
"url": "https://github.com/run-llama/create-llama"
},
"license": "MIT",
"bin": {
"create-llama": "./dist/index.js"
},
"files": [
"dist"
"workspaces": [
"packages/*",
"python/*"
],
"scripts": {
"build": "bash ./scripts/build.sh",
"build:ncc": "pnpm run clean && ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
"clean": "rimraf --glob ./dist ./templates/**/__pycache__ ./templates/**/node_modules ./templates/**/poetry.lock",
"dev": "ncc build ./index.ts -w -o dist/",
"e2e": "playwright test",
"dev": "pnpm -r dev",
"build": "pnpm -r build",
"e2e": "pnpm -r e2e",
"lint": "eslint .",
"format": "prettier --ignore-unknown --cache --check .",
"format:write": "prettier --ignore-unknown --write .",
"lint": "eslint . --ignore-pattern dist --ignore-pattern e2e/cache",
"new-snapshot": "pnpm run build && changeset version --snapshot",
"new-version": "pnpm run build && changeset version",
"pack-install": "bash ./scripts/pack.sh",
"prepare": "husky",
"release": "pnpm run build && changeset publish",
"release-snapshot": "pnpm run build && changeset publish --tag snapshot"
},
"dependencies": {
"@types/async-retry": "1.4.2",
"@types/ci-info": "2.0.0",
"@types/cross-spawn": "6.0.0",
"@types/fs-extra": "11.0.4",
"@types/node": "^20.11.7",
"@types/prompts": "2.0.1",
"@types/tar": "6.1.5",
"@types/validate-npm-package-name": "3.0.0",
"async-retry": "1.3.1",
"async-sema": "3.0.1",
"ci-info": "github:watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540",
"commander": "2.20.0",
"conf": "10.2.0",
"cross-spawn": "7.0.3",
"fast-glob": "3.3.1",
"fs-extra": "11.2.0",
"global-agent": "^3.0.0",
"got": "10.7.0",
"ollama": "^0.5.0",
"ora": "^8.0.1",
"picocolors": "1.0.0",
"prompts": "2.1.0",
"smol-toml": "^1.1.4",
"tar": "6.1.15",
"terminal-link": "^3.0.0",
"update-check": "1.5.4",
"validate-npm-package-name": "3.0.0",
"yaml": "2.4.1"
"new-snapshot": "pnpm -r build && changeset version --snapshot",
"new-version-python": "pnpm --filter @create-llama/llama-index-server new-version",
"new-version": "pnpm -r build && changeset version && pnpm new-version-python",
"release-python": "pnpm --filter @create-llama/llama-index-server release",
"release": "pnpm -r build && changeset publish && pnpm release-python",
"release-snapshot": "pnpm -r build && changeset publish --tag snapshot"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
"@playwright/test": "^1.41.1",
"@vercel/ncc": "0.38.1",
"eslint": "^8.56.0",
"eslint-config-prettier": "^8.10.0",
"bunchee": "6.4.0",
"husky": "^9.0.10",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
"rimraf": "^5.0.5",
"typescript": "^5.3.3",
"wait-port": "^1.1.0"
"lint-staged": "^15.2.11",
"typescript-eslint": "^8.18.0",
"globals": "^15.12.0",
"eslint": "9.22.0",
"@eslint/js": "^9.25.0",
"eslint-config-next": "^15.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react": "7.37.2",
"prettier": "^3.4.2",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-tailwindcss": "^0.6.11",
"typescript": "^5.7.3",
"@types/node": "^22.9.0",
"@types/react": "^19",
"@types/react-dom": "^19"
},
"packageManager": "pnpm@9.0.5",
"engines": {
+65
View File
@@ -0,0 +1,65 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
node_modules
.pnp
.pnpm-store
.pnp.js
# testing
coverage
.coverage
# next.js
.next/
out/
build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# build
dist/
lib/
# e2e
.cache
test-results/
playwright-report/
blob-report/
playwright/.cache/
.tsbuildinfo
e2e/cache
# intellij
**/.idea
# Python
.mypy_cache/
venv/
.venv/
dist/
.__pycache__
__pycache__
.python-version
.ui
# build artifacts
create-llama-*.tgz
# copied from root
README.md
LICENSE.md
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
# create-llama Package
## Overview
The `create-llama` package is a CLI tool for creating LlamaIndex-powered applications with one command. It's designed as a project generator that scaffolds various types of RAG (Retrieval-Augmented Generation) applications using different frameworks, databases, and AI model providers.
## Package Structure
### Core Files
- **`index.ts`**: Main CLI entry point using Commander.js for argument parsing
- **`create-app.ts`**: Core application creation logic and orchestration
- **`package.json`**: Package configuration with binary entry point at `./dist/index.js`
### Key Directories
- **`helpers/`**: Utility functions for package management, file operations, and configuration
- **`questions/`**: Interactive prompts for user configuration
- **`templates/`**: Project templates for different frameworks and use cases
- **`e2e/`**: End-to-end tests using Playwright
## Core Functionality
### CLI Interface
The tool accepts numerous command-line options including:
- Framework selection (`--framework`: nextjs, express, fastapi)
- Template type (`--template`: streaming, multiagent, reflex, llamaindexserver)
- Model providers (OpenAI, Anthropic, Groq, Ollama, etc.)
- Vector databases (none, mongo, pg, pinecone, milvus, etc.)
- Data sources (files, web URLs, databases)
- Tools and observability options
### Application Generation Flow
1. **Project validation**: Checks project name validity and directory permissions
2. **Interactive questioning**: Prompts user for configuration if not provided via CLI
3. **Template installation**: Copies and configures appropriate templates
4. **Environment setup**: Creates `.env` files with API keys and configuration
5. **Dependencies**: Installs packages using detected/specified package manager
6. **Post-install actions**: Can run the app, open VSCode, or install dependencies
### Template System
Templates are organized by:
- **Framework**: NextJS (frontend), Express (Node backend), FastAPI (Python backend)
- **Type**: Streaming chat, multiagent workflows, Reflex UI, LlamaIndex server
- **Components**: Engines, loaders, providers, UI components, observability
### Helper Functions
Key helper modules include:
- **Installation**: Package manager detection and dependency installation
- **Data sources**: File copying, web scraping, database connection setup
- **Providers**: Model provider configuration (OpenAI, Anthropic, etc.)
- **Tools**: Integration with external tools (Wikipedia, weather, code generation)
- **Environment**: `.env` file generation with API keys and settings
## Development Commands
### Build & Development
- `npm run build`: Build the CLI using bash script
- `npm run dev`: Watch mode development build
- `npm run clean`: Clean build artifacts and temporary files
### Testing
- `npm run e2e`: Run all end-to-end tests
- `npm run e2e:python`: Test Python-specific templates
- `npm run e2e:typescript`: Test TypeScript-specific templates
### Package Management
- `npm run pack-install`: Create and install local package for testing
## Architecture Notes
### Model Configuration
The tool supports multiple AI providers with a unified `ModelConfig` interface that includes:
- Provider selection and API key management
- Model and embedding model specification
- Dimension configuration for embeddings
### Data Source Handling
Flexible data source configuration supporting:
- Local files and directories
- Web URLs with configurable crawling depth
- Database connections with custom queries
- Automatic file downloading and copying
### Template Flexibility
Templates use a component-based system allowing mix-and-match of:
- Different frameworks (NextJS, Express, FastAPI)
- Various vector databases
- Multiple observability tools
- Configurable tools and integrations
This package serves as the foundation for rapidly prototyping and deploying LlamaIndex applications across different technology stacks and use cases.
+107
View File
@@ -0,0 +1,107 @@
import path from "path";
import { green, yellow } from "picocolors";
import { tryGitInit } from "./helpers/git";
import { isFolderEmpty } from "./helpers/is-folder-empty";
import { isWriteable } from "./helpers/is-writeable";
import { makeDir } from "./helpers/make-dir";
import terminalLink from "terminal-link";
import type { InstallTemplateArgs } from "./helpers";
import { installTemplate } from "./helpers";
import { templatesDir } from "./helpers/dir";
import { configVSCode } from "./helpers/vscode";
export type InstallAppArgs = Omit<
InstallTemplateArgs,
"appName" | "root" | "port"
> & {
appPath: string;
};
export async function createApp({
template,
framework,
appPath,
packageManager,
modelConfig,
llamaCloudKey,
vectorDb,
postInstallAction,
dataSources,
useLlamaParse,
useCase,
}: InstallAppArgs): Promise<void> {
const root = path.resolve(appPath);
if (!(await isWriteable(path.dirname(root)))) {
console.error(
"The application path is not writable, please check folder permissions and try again.",
);
console.error(
"It is likely you do not have write permissions for this folder.",
);
process.exit(1);
}
const appName = path.basename(root);
await makeDir(root);
if (!isFolderEmpty(root, appName)) {
process.exit(1);
}
console.log(`Creating a new LlamaIndex app in ${green(root)}.`);
console.log();
const args = {
appName,
root,
template,
framework,
packageManager,
modelConfig,
llamaCloudKey,
vectorDb,
postInstallAction,
dataSources,
useLlamaParse,
useCase,
};
// Install backend
await installTemplate(args);
await configVSCode(root, templatesDir, framework);
process.chdir(root);
if (tryGitInit(root)) {
console.log("Initialized a git repository.");
console.log();
}
console.log("");
console.log(`${green("Success!")} Created ${appName} at ${appPath}`);
console.log(
`Now have a look at the ${terminalLink(
"README.md",
`file://${root}/README.md`,
)} and learn how to get started.`,
);
if (
dataSources.some((dataSource) => dataSource.type === "file") &&
process.platform === "linux"
) {
console.log(
yellow(
`You can add your own data files to ${terminalLink(
"data",
`file://${root}/data`,
)} folder manually.`,
),
);
}
console.log();
}
@@ -0,0 +1,110 @@
import { expect, test } from "@playwright/test";
import { exec } from "child_process";
import fs from "fs";
import path from "path";
import util from "util";
import {
ALL_USE_CASES,
TemplateFramework,
TemplateVectorDB,
} from "../../helpers/types";
import { RunCreateLlamaOptions, createTestDir, runCreateLlama } from "../utils";
const execAsync = util.promisify(exec);
const templateFramework: TemplateFramework = "fastapi";
const vectorDb: TemplateVectorDB = process.env.VECTORDB
? (process.env.VECTORDB as TemplateVectorDB)
: "none";
test.describe("Mypy check", () => {
test.describe.configure({ retries: 0 });
test.describe("LlamaIndexServer", async () => {
for (const useCase of ALL_USE_CASES) {
test(`should pass mypy for use case: ${useCase}`, async () => {
const cwd = await createTestDir();
await createAndCheckLlamaProject({
options: {
cwd,
templateFramework,
vectorDb,
port: 3000,
postInstallAction: "none",
llamaCloudProjectName: undefined,
llamaCloudIndexName: undefined,
useCase,
},
});
});
}
});
});
async function createAndCheckLlamaProject({
options,
}: {
options: RunCreateLlamaOptions;
}): Promise<{ pyprojectPath: string; projectPath: string }> {
const result = await runCreateLlama(options);
const name = result.projectName;
const projectPath = path.join(options.cwd, name);
// Check if the app folder exists
expect(fs.existsSync(projectPath)).toBeTruthy();
// Check if pyproject.toml exists
const pyprojectPath = path.join(projectPath, "pyproject.toml");
expect(fs.existsSync(pyprojectPath)).toBeTruthy();
// Modify environment for the command
const commandEnv = {
...process.env,
};
console.log("Running uv venv...");
try {
const { stdout: venvStdout, stderr: venvStderr } = await execAsync(
"uv venv",
{ cwd: projectPath, env: commandEnv },
);
console.log("uv venv stdout:", venvStdout);
console.error("uv venv stderr:", venvStderr);
} catch (error) {
console.error("Error running uv venv:", error);
throw error; // Re-throw error to fail the test
}
console.log("Running uv sync...");
try {
const { stdout: syncStdout, stderr: syncStderr } = await execAsync(
"uv sync --all-extras",
{ cwd: projectPath, env: commandEnv },
);
console.log("uv sync stdout:", syncStdout);
console.error("uv sync stderr:", syncStderr);
} catch (error) {
console.error("Error running uv sync:", error);
throw error; // Re-throw error to fail the test
}
console.log("Running uv run mypy ....");
try {
const { stdout: mypyStdout, stderr: mypyStderr } = await execAsync(
"uv run mypy .",
{ cwd: projectPath, env: commandEnv },
);
console.log("uv run mypy stdout:", mypyStdout);
console.error("uv run mypy stderr:", mypyStderr);
// Assuming mypy success means no output or specific success message
// Adjust checks based on actual expected mypy output
} catch (error) {
console.error("Error running mypy:", error);
throw error;
}
// If we reach this point without throwing an error, the test passes
expect(true).toBeTruthy();
return { pyprojectPath, projectPath };
}
@@ -0,0 +1,90 @@
import { expect, test } from "@playwright/test";
import { ChildProcess } from "child_process";
import fs from "fs";
import path from "path";
import {
ALL_USE_CASES,
type TemplateFramework,
type TemplateVectorDB,
} from "../../helpers";
import { createTestDir, runCreateLlama } from "../utils";
const templateFramework: TemplateFramework = process.env.FRAMEWORK
? (process.env.FRAMEWORK as TemplateFramework)
: "fastapi";
const vectorDb: TemplateVectorDB = process.env.VECTORDB
? (process.env.VECTORDB as TemplateVectorDB)
: "none";
const llamaCloudProjectName = "create-llama";
const llamaCloudIndexName = "e2e-test";
const userMessage = "Write a blog post about physical standards for letters";
for (const useCase of ALL_USE_CASES) {
test.describe(`Test use case ${useCase} ${templateFramework} ${vectorDb}`, async () => {
let port: number;
let cwd: string;
let name: string;
let appProcess: ChildProcess;
test.beforeAll(async () => {
port = Math.floor(Math.random() * 10000) + 10000;
cwd = await createTestDir();
const result = await runCreateLlama({
cwd,
templateFramework,
vectorDb,
port,
postInstallAction: "runApp",
useCase,
llamaCloudProjectName,
llamaCloudIndexName,
});
name = result.projectName;
appProcess = result.appProcess;
});
test("App folder should exist", async () => {
const dirExists = fs.existsSync(path.join(cwd, name));
expect(dirExists).toBeTruthy();
});
test("Frontend should have a title", async ({ page }) => {
await page.goto(`http://localhost:${port}`);
await expect(page.getByText("Built by LlamaIndex")).toBeVisible({
timeout: 5 * 60 * 1000,
});
});
test("Frontend should be able to submit a message and receive the start of a streamed response", async ({
page,
}) => {
test.skip(
useCase === "financial_report" || useCase === "deep_research",
"Skip chat tests for financial report and deep research.",
);
await page.goto(`http://localhost:${port}`);
await page.fill("form textarea", userMessage);
const responsePromise = page.waitForResponse((res) =>
res.url().includes("/api/chat"),
);
await page.click("form button[type=submit]");
const response = await responsePromise;
console.log(`Response status: ${response.status()}`);
const responseBody = await response
.text()
.catch((e) => `Error reading body: ${e}`);
console.log(`Response body: ${responseBody}`);
expect(response.ok()).toBeTruthy();
});
// clean processes
test.afterAll(async () => {
appProcess?.kill();
});
});
}
@@ -0,0 +1,70 @@
import { expect, test } from "@playwright/test";
import { ChildProcess, execSync } from "child_process";
import fs from "fs";
import path from "path";
import { type TemplateFramework, type TemplateVectorDB } from "../../helpers";
import { createTestDir, runCreateLlama } from "../utils";
const templateFramework: TemplateFramework = "nextjs";
const useCase = "code_generator";
const vectorDb: TemplateVectorDB = process.env.VECTORDB
? (process.env.VECTORDB as TemplateVectorDB)
: "none";
const llamaCloudProjectName = "create-llama";
const llamaCloudIndexName = "e2e-test";
const ejectDir = "next";
test.describe.skip(
`Test eject command for ${useCase} ${templateFramework} ${vectorDb}`,
async () => {
let port: number;
let cwd: string;
let name: string;
let appProcess: ChildProcess;
test.beforeAll(async () => {
port = Math.floor(Math.random() * 10000) + 10000;
cwd = await createTestDir();
const result = await runCreateLlama({
cwd,
templateFramework,
vectorDb,
port,
postInstallAction: "dependencies",
useCase,
llamaCloudProjectName,
llamaCloudIndexName,
});
name = result.projectName;
appProcess = result.appProcess;
});
test("Should successfully eject, install dependencies and build without errors", async ({
page,
}) => {
test.skip(
vectorDb === "llamacloud",
"Eject test only works with non-llamacloud",
);
// Run eject command
execSync("npm run eject", { cwd: path.join(cwd, name) });
// Verify next directory exists
const nextDirExists = fs.existsSync(path.join(cwd, name, ejectDir));
expect(nextDirExists).toBeTruthy();
// Install dependencies in next directory
execSync("npm install", { cwd: path.join(cwd, name, ejectDir) });
// Run build
execSync("npm run build", { cwd: path.join(cwd, name, ejectDir) });
});
// clean processes
test.afterAll(async () => {
appProcess?.kill();
});
},
);
@@ -0,0 +1,90 @@
import { expect, test } from "@playwright/test";
import { exec } from "child_process";
import fs from "fs";
import path from "path";
import util from "util";
import {
ALL_USE_CASES,
TemplateFramework,
TemplateUseCase,
TemplateVectorDB,
} from "../../helpers/types";
import { createTestDir, runCreateLlama } from "../utils";
const execAsync = util.promisify(exec);
const templateFramework: TemplateFramework = "nextjs";
const vectorDb: TemplateVectorDB = process.env.VECTORDB
? (process.env.VECTORDB as TemplateVectorDB)
: "none";
test.describe("Test resolve TS dependencies", () => {
test.describe.configure({ retries: 0 });
for (const useCase of ALL_USE_CASES) {
const optionDescription = `useCase: ${useCase}, vectorDb: ${vectorDb}`;
test.describe(`${optionDescription}`, () => {
test(`${optionDescription}`, async () => {
await runTest({
useCase: useCase,
vectorDb: vectorDb,
});
});
});
}
});
async function runTest(options: {
useCase: TemplateUseCase;
vectorDb: TemplateVectorDB;
}) {
const cwd = await createTestDir();
const result = await runCreateLlama({
cwd: cwd,
templateFramework: templateFramework,
vectorDb: options.vectorDb,
port: 3000,
postInstallAction: "none",
llamaCloudProjectName: undefined,
llamaCloudIndexName: undefined,
useCase: options.useCase,
});
const name = result.projectName;
// Check if the app folder exists
const appDir = path.join(cwd, name);
const dirExists = fs.existsSync(appDir);
expect(dirExists).toBeTruthy();
// Install dependencies using pnpm
try {
const { stderr: installStderr } = await execAsync(
"pnpm install --prefer-offline --ignore-workspace",
{
cwd: appDir,
},
);
} catch (error) {
console.error("Error installing dependencies:", error);
throw error;
}
// Run tsc type check and capture the output
try {
const { stdout, stderr } = await execAsync(
"pnpm exec tsc -b --diagnostics",
{
cwd: appDir,
},
);
// Check if there's any error output
expect(stderr).toBeFalsy();
// Log the stdout for debugging purposes
console.log("TypeScript type-check output:", stdout);
} catch (error) {
console.error("Error running tsc:", error);
throw error;
}
}
+136
View File
@@ -0,0 +1,136 @@
import { ChildProcess, exec } from "child_process";
import crypto from "node:crypto";
import { mkdir } from "node:fs/promises";
import * as path from "path";
import waitPort from "wait-port";
import {
TemplateFramework,
TemplatePostInstallAction,
TemplateVectorDB,
} from "../helpers";
export type CreateLlamaResult = {
projectName: string;
appProcess: ChildProcess;
};
export type RunCreateLlamaOptions = {
cwd: string;
templateFramework: TemplateFramework;
vectorDb: TemplateVectorDB;
port: number;
postInstallAction: TemplatePostInstallAction;
useCase: string;
llamaCloudProjectName?: string;
llamaCloudIndexName?: string;
};
export async function runCreateLlama({
cwd,
templateFramework,
vectorDb,
port,
postInstallAction,
useCase,
llamaCloudProjectName,
llamaCloudIndexName,
}: RunCreateLlamaOptions): Promise<CreateLlamaResult> {
if (!process.env.OPENAI_API_KEY || !process.env.LLAMA_CLOUD_API_KEY) {
throw new Error(
"Setting the OPENAI_API_KEY and LLAMA_CLOUD_API_KEY is mandatory to run tests",
);
}
const name = [templateFramework, useCase, vectorDb].join("-");
const commandArgs = [
"create-llama",
name,
"--framework",
templateFramework,
"--vector-db",
vectorDb,
"--use-npm",
"--port",
port,
"--post-install-action",
postInstallAction,
"--use-case",
useCase,
];
const command = commandArgs.join(" ");
console.log(`running command '${command}' in ${cwd}`);
const appProcess = exec(command, {
cwd,
env: {
...process.env,
LLAMA_CLOUD_PROJECT_NAME: llamaCloudProjectName,
LLAMA_CLOUD_INDEX_NAME: llamaCloudIndexName,
},
});
appProcess.stderr?.on("data", (data) => {
console.error(data.toString());
});
appProcess.on("exit", (code) => {
if (code !== 0 && code !== null) {
throw new Error(`create-llama command failed with exit code ${code}`);
}
});
// Wait for app to start
if (postInstallAction === "runApp") {
await waitPorts([port]);
} else if (postInstallAction === "dependencies") {
await waitForProcess(appProcess, 1000 * 60); // wait 1 min for dependencies to be resolved
} else {
// wait 10 seconds for create-llama to exit
await waitForProcess(appProcess, 1000 * 10);
}
return {
projectName: name,
appProcess,
};
}
export async function createTestDir() {
const cwd = path.join(__dirname, "cache", crypto.randomUUID());
await mkdir(cwd, { recursive: true });
return cwd;
}
async function waitPorts(ports: number[]): Promise<void> {
const waitForPort = async (port: number): Promise<void> => {
await waitPort({
host: "localhost",
port: port,
// wait max. 5 mins for start up of app
timeout: 1000 * 60 * 5,
});
};
try {
await Promise.all(ports.map(waitForPort));
} catch (err) {
console.error(err);
throw err;
}
}
async function waitForProcess(
process: ChildProcess,
timeoutMs: number,
): Promise<void> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Process timeout error"));
}, timeoutMs);
process.on("exit", (code) => {
clearTimeout(timeout);
if (code !== 0 && code !== null) {
reject(new Error("Process exited with non-zero code"));
} else {
resolve();
}
});
});
}
@@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import { async as glob } from "fast-glob";
import fs from "fs";
import path from "path";
@@ -61,6 +60,9 @@ export const assetRelocator = (name: string) => {
case "README-template.md": {
return "README.md";
}
case "vscode_settings.json": {
return "settings.json";
}
default: {
return name;
}
@@ -0,0 +1,51 @@
import path from "path";
import { templatesDir } from "./dir";
import { TemplateDataSource } from "./types";
export const EXAMPLE_FILE: TemplateDataSource = {
type: "file",
config: {
path: path.join(templatesDir, "components", "data", "101.pdf"),
},
};
export const EXAMPLE_10K_SEC_FILES: TemplateDataSource[] = [
{
type: "file",
config: {
url: new URL(
"https://s2.q4cdn.com/470004039/files/doc_earnings/2023/q4/filing/_10-K-Q4-2023-As-Filed.pdf",
),
filename: "apple_10k_report.pdf",
},
},
{
type: "file",
config: {
url: new URL(
"https://ir.tesla.com/_flysystem/s3/sec/000162828024002390/tsla-20231231-gen.pdf",
),
filename: "tesla_10k_report.pdf",
},
},
];
export const EXAMPLE_GDPR: TemplateDataSource = {
type: "file",
config: {
url: new URL(
"https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:32016R0679",
),
filename: "gdpr.pdf",
},
};
export const AI_REPORTS: TemplateDataSource = {
type: "file",
config: {
url: new URL(
"https://www.europarl.europa.eu/RegData/etudes/ATAG/2024/760392/EPRS_ATA(2024)760392_EN.pdf",
),
filename: "EPRS_ATA_2024_760392_EN.pdf",
},
};
@@ -1,13 +1,15 @@
import fs from "fs/promises";
import path from "path";
import { TOOL_SYSTEM_PROMPT_ENV_VAR, Tool } from "./tools";
import {
InstallTemplateArgs,
ModelConfig,
TemplateDataSource,
TemplateFramework,
TemplateType,
TemplateVectorDB,
} from "./types";
import { TSYSTEMS_LLMHUB_API_URL } from "./providers/llmhub";
export type EnvVar = {
name?: string;
description?: string;
@@ -33,6 +35,7 @@ const renderEnvVar = (envVars: EnvVar[]): string => {
const getVectorDBEnvs = (
vectorDb?: TemplateVectorDB,
framework?: TemplateFramework,
template?: TemplateType,
): EnvVar[] => {
if (!vectorDb || !framework) {
return [];
@@ -60,7 +63,7 @@ const getVectorDBEnvs = (
{
name: "PG_CONNECTION_STRING",
description:
"For generating a connection URI, see https://docs.timescale.com/use-timescale/latest/services/create-a-service\nThe PostgreSQL connection string.",
"For generating a connection URI, see https://supabase.com/vector\nThe PostgreSQL connection string.",
},
];
@@ -133,7 +136,43 @@ const getVectorDBEnvs = (
"Optional API key for authenticating requests to Qdrant.",
},
];
case "chroma":
case "llamacloud":
return [
{
name: "LLAMA_CLOUD_INDEX_NAME",
description:
"The name of the LlamaCloud index to use (part of the LlamaCloud project).",
value: "test",
},
{
name: "LLAMA_CLOUD_PROJECT_NAME",
description: "The name of the LlamaCloud project.",
value: "Default",
},
{
name: "LLAMA_CLOUD_BASE_URL",
description:
"The base URL for the LlamaCloud API. Only change this for non-production environments",
value: "https://api.cloud.llamaindex.ai",
},
{
name: "LLAMA_CLOUD_ORGANIZATION_ID",
description:
"The organization ID for the LlamaCloud project (uses default organization if not specified)",
},
...(framework === "nextjs" && template !== "llamaindexserver"
? // activate index selector per default (not needed for non-NextJS backends as it's handled by createFrontendEnvFile)
[
{
name: "NEXT_PUBLIC_USE_LLAMACLOUD",
description:
"Let's the user change indexes in LlamaCloud projects",
value: "true",
},
]
: []),
];
case "chroma": {
const envs = [
{
name: "CHROMA_COLLECTION",
@@ -141,11 +180,11 @@ const getVectorDBEnvs = (
},
{
name: "CHROMA_HOST",
description: "The API endpoint for your Chroma database",
description: "The hostname for your Chroma database. Eg: localhost",
},
{
name: "CHROMA_PORT",
description: "The port for your Chroma database",
description: "The port for your Chroma database. Eg: 8000",
},
];
// TS Version doesn't support config local storage path
@@ -158,18 +197,39 @@ Otherwise, use CHROMA_HOST and CHROMA_PORT config above`,
});
}
return envs;
}
case "weaviate":
return [
{
name: "WEAVIATE_CLUSTER_URL",
description:
"The URL of the Weaviate cloud cluster, see: https://weaviate.io/developers/wcs/connect",
},
{
name: "WEAVIATE_API_KEY",
description: "The API key for the Weaviate cloud cluster",
},
{
name: "WEAVIATE_INDEX_NAME",
description:
"(Optional) The collection name to use, default is LlamaIndex if not specified",
},
];
default:
return [];
return template !== "llamaindexserver"
? [
{
name: "STORAGE_CACHE_DIR",
description: "The directory to store the local storage cache.",
value: ".cache",
},
]
: [];
}
};
const getModelEnvs = (modelConfig: ModelConfig): EnvVar[] => {
return [
{
name: "MODEL_PROVIDER",
description: "The provider for the AI models to use.",
value: modelConfig.provider,
},
{
name: "MODEL",
description: "The name of LLM model to use.",
@@ -181,9 +241,8 @@ const getModelEnvs = (modelConfig: ModelConfig): EnvVar[] => {
value: modelConfig.embeddingModel,
},
{
name: "EMBEDDING_DIM",
description: "Dimension of the embedding model to use.",
value: modelConfig.dimensions.toString(),
name: "CONVERSATION_STARTERS",
description: "The questions to help users get started (multi-line).",
},
...(modelConfig.provider === "openai"
? [
@@ -211,6 +270,15 @@ const getModelEnvs = (modelConfig: ModelConfig): EnvVar[] => {
},
]
: []),
...(modelConfig.provider === "groq"
? [
{
name: "GROQ_API_KEY",
description: "The Groq API key to use.",
value: modelConfig.apiKey,
},
]
: []),
...(modelConfig.provider === "gemini"
? [
{
@@ -229,6 +297,71 @@ const getModelEnvs = (modelConfig: ModelConfig): EnvVar[] => {
},
]
: []),
...(modelConfig.provider === "mistral"
? [
{
name: "MISTRAL_API_KEY",
description: "The Mistral API key to use.",
value: modelConfig.apiKey,
},
]
: []),
...(modelConfig.provider === "azure-openai"
? [
{
name: "AZURE_OPENAI_API_KEY",
description: "The Azure OpenAI key to use.",
value: modelConfig.apiKey,
},
{
name: "AZURE_OPENAI_ENDPOINT",
description: "The Azure OpenAI endpoint to use.",
},
{
name: "AZURE_OPENAI_API_VERSION",
description: "The Azure OpenAI API version to use.",
},
{
name: "AZURE_OPENAI_LLM_DEPLOYMENT",
description:
"The Azure OpenAI deployment to use for LLM deployment.",
},
{
name: "AZURE_OPENAI_EMBEDDING_DEPLOYMENT",
description:
"The Azure OpenAI deployment to use for embedding deployment.",
},
]
: []),
...(modelConfig.provider === "huggingface"
? [
{
name: "EMBEDDING_BACKEND",
description:
"The backend to use for the Sentence Transformers embedding model, either 'torch', 'onnx', or 'openvino'. Defaults to 'onnx'.",
},
{
name: "EMBEDDING_TRUST_REMOTE_CODE",
description:
"Whether to trust remote code for the embedding model, required for some models with custom code.",
},
]
: []),
...(modelConfig.provider === "t-systems"
? [
{
name: "T_SYSTEMS_LLMHUB_BASE_URL",
description:
"The base URL for the T-Systems AI Foundation Model API. Eg: http://localhost:11434",
value: TSYSTEMS_LLMHUB_API_URL,
},
{
name: "T_SYSTEMS_LLMHUB_API_KEY",
description: "API Key for T-System's AI Foundation Model.",
value: modelConfig.apiKey,
},
]
: []),
];
};
@@ -237,142 +370,59 @@ const getFrameworkEnvs = (
port?: number,
): EnvVar[] => {
const sPort = port?.toString() || "8000";
const result: EnvVar[] = [
{
name: "FILESERVER_URL_PREFIX",
description:
"FILESERVER_URL_PREFIX is the URL prefix of the server storing the images generated by the interpreter.",
value:
framework === "nextjs"
? // FIXME: if we are using nextjs, port should be 3000
"http://localhost:3000/api/files"
: `http://localhost:${sPort}/api/files`,
},
];
const result: EnvVar[] = [];
if (framework === "fastapi") {
result.push(
...[
{
name: "APP_HOST",
description: "The address to start the backend app.",
description: "The address to start the FastAPI app.",
value: "0.0.0.0",
},
{
name: "APP_PORT",
description: "The port to start the backend app.",
description: "The port to start the FastAPI app.",
value: sPort,
},
],
);
}
return result;
};
const getEngineEnvs = (): EnvVar[] => {
return [
{
name: "TOP_K",
description:
"The number of similar embeddings to return when retrieving documents.",
value: "3",
},
];
};
const getToolEnvs = (tools?: Tool[]): EnvVar[] => {
if (!tools?.length) return [];
const toolEnvs: EnvVar[] = [];
tools.forEach((tool) => {
if (tool.envVars?.length) {
toolEnvs.push(
// Don't include the system prompt env var here
// It should be handled separately by merging with the default system prompt
...tool.envVars.filter(
(env) => env.name !== TOOL_SYSTEM_PROMPT_ENV_VAR,
),
);
}
});
return toolEnvs;
};
const getSystemPromptEnv = (tools?: Tool[]): EnvVar => {
const defaultSystemPrompt =
"You are a helpful assistant who helps users with their questions.";
// build tool system prompt by merging all tool system prompts
let toolSystemPrompt = "";
tools?.forEach((tool) => {
const toolSystemPromptEnv = tool.envVars?.find(
(env) => env.name === TOOL_SYSTEM_PROMPT_ENV_VAR,
);
if (toolSystemPromptEnv) {
toolSystemPrompt += toolSystemPromptEnv.value + "\n";
}
});
const systemPrompt = toolSystemPrompt
? `\"${toolSystemPrompt}\"`
: defaultSystemPrompt;
return {
name: "SYSTEM_PROMPT",
description: "The system prompt for the AI model.",
value: systemPrompt,
};
};
export const createBackendEnvFile = async (
root: string,
opts: {
llamaCloudKey?: string;
vectorDb?: TemplateVectorDB;
modelConfig: ModelConfig;
framework: TemplateFramework;
dataSources?: TemplateDataSource[];
port?: number;
tools?: Tool[];
},
opts: Pick<
InstallTemplateArgs,
| "llamaCloudKey"
| "vectorDb"
| "modelConfig"
| "framework"
| "dataSources"
| "template"
| "port"
| "useLlamaParse"
>,
) => {
// Init env values
const envFileName = ".env";
const envVars: EnvVar[] = [
{
name: "LLAMA_CLOUD_API_KEY",
description: `The Llama Cloud API key.`,
value: opts.llamaCloudKey,
},
// Add model environment variables
...getModelEnvs(opts.modelConfig),
// Add engine environment variables
...getEngineEnvs(),
// Add vector database environment variables
...getVectorDBEnvs(opts.vectorDb, opts.framework),
...(opts.useLlamaParse
? [
{
name: "LLAMA_CLOUD_API_KEY",
description: `The Llama Cloud API key.`,
value: opts.llamaCloudKey,
},
]
: []),
...getVectorDBEnvs(opts.vectorDb, opts.framework, opts.template),
...getFrameworkEnvs(opts.framework, opts.port),
...getToolEnvs(opts.tools),
getSystemPromptEnv(opts.tools),
...getModelEnvs(opts.modelConfig),
];
// Render and write env file
const content = renderEnvVar(envVars);
await fs.writeFile(path.join(root, envFileName), content);
console.log(`Created '${envFileName}' file. Please check the settings.`);
};
export const createFrontendEnvFile = async (
root: string,
opts: {
customApiPath?: string;
},
) => {
const defaultFrontendEnvs = [
{
name: "NEXT_PUBLIC_CHAT_API",
description: "The backend API for chat endpoint.",
value: opts.customApiPath
? opts.customApiPath
: "http://localhost:8000/api/chat",
},
];
const content = renderEnvVar(defaultFrontendEnvs);
await fs.writeFile(path.join(root, ".env"), content);
};
@@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import { execSync } from "child_process";
import fs from "fs";
import path from "path";
+192
View File
@@ -0,0 +1,192 @@
import { callPackageManager } from "./install";
import path from "path";
import picocolors, { cyan } from "picocolors";
import fsExtra from "fs-extra";
import { createBackendEnvFile } from "./env-variables";
import { PackageManager } from "./get-pkg-manager";
import { makeDir } from "./make-dir";
import { installPythonTemplate } from "./python";
import {
FileSourceConfig,
InstallTemplateArgs,
ModelConfig,
TemplateDataSource,
TemplateFramework,
TemplateVectorDB,
} from "./types";
import { installTSTemplate } from "./typescript";
import { isHavingUvLockFile, tryUvRun } from "./uv";
const checkForGenerateScript = (
modelConfig: ModelConfig,
vectorDb?: TemplateVectorDB,
llamaCloudKey?: string,
useLlamaParse?: boolean,
) => {
const missingSettings = [];
if (!modelConfig.isConfigured()) {
missingSettings.push("your model provider API key");
}
const llamaCloudApiKey = llamaCloudKey ?? process.env["LLAMA_CLOUD_API_KEY"];
const isRequiredLlamaCloudKey = useLlamaParse || vectorDb === "llamacloud";
if (isRequiredLlamaCloudKey && !llamaCloudApiKey) {
missingSettings.push("your LLAMA_CLOUD_API_KEY");
}
if (
vectorDb !== undefined &&
vectorDb !== "none" &&
vectorDb !== "llamacloud"
) {
missingSettings.push("your Vector DB environment variables");
}
return missingSettings;
};
// eslint-disable-next-line max-params
async function generateContextData(
framework: TemplateFramework,
modelConfig: ModelConfig,
dataSources: TemplateDataSource[],
packageManager?: PackageManager,
vectorDb?: TemplateVectorDB,
llamaCloudKey?: string,
useLlamaParse?: boolean,
) {
if (packageManager) {
const runGenerate = `${cyan(
framework === "fastapi"
? "uv run generate"
: `${packageManager} run generate`,
)}`;
const missingSettings = checkForGenerateScript(
modelConfig,
vectorDb,
llamaCloudKey,
useLlamaParse,
);
if (!missingSettings.length) {
// If all the required environment variables are set, run the generate script
if (framework === "fastapi") {
if (isHavingUvLockFile()) {
console.log(`Running ${runGenerate} to generate the context data.`);
const result = tryUvRun("generate");
if (!result) {
console.log(`Failed to run ${runGenerate}.`);
process.exit(1);
}
console.log(`Generated context data`);
return;
} else {
console.log(
picocolors.yellow(
`\nWarning: uv.lock not found. Dependency installation might be incomplete. Skipping context generation.\nIf dependencies were installed, try running '${runGenerate}' manually.\n`,
),
);
}
} else {
console.log(`Running ${runGenerate} to generate the context data.`);
const shouldRunGenerate = dataSources.length > 0;
if (shouldRunGenerate) {
await callPackageManager(packageManager, true, ["run", "generate"]);
}
return;
}
}
const settingsMessage = `After setting ${missingSettings.join(" and ")}, run ${runGenerate} to generate the context data.`;
console.log(picocolors.yellow(`\n${settingsMessage}\n\n`));
}
}
const downloadFile = async (url: string, destPath: string) => {
const response = await fetch(url);
const fileBuffer = await response.arrayBuffer();
await fsExtra.writeFile(destPath, new Uint8Array(fileBuffer));
};
const prepareContextData = async (
root: string,
dataSources: TemplateDataSource[],
) => {
await makeDir(path.join(root, "data"));
for (const dataSource of dataSources) {
const dataSourceConfig = dataSource?.config as FileSourceConfig;
// If the path is URLs, download the data and save it to the data directory
if ("url" in dataSourceConfig) {
console.log(
"Downloading file from URL:",
dataSourceConfig.url.toString(),
);
const destPath = path.join(
root,
"data",
dataSourceConfig.filename ??
path.basename(dataSourceConfig.url.toString()),
);
await downloadFile(dataSourceConfig.url.toString(), destPath);
} else {
// Copy local data
console.log("Copying data from path:", dataSourceConfig.path);
const destPath = path.join(
root,
"data",
path.basename(dataSourceConfig.path),
);
await fsExtra.copy(dataSourceConfig.path, destPath);
}
}
};
export const installTemplate = async (props: InstallTemplateArgs) => {
process.chdir(props.root);
if (props.framework === "fastapi") {
await installPythonTemplate(props);
} else {
await installTSTemplate(props);
}
// This is a backend, so we need to copy the test data and create the env file.
// Copy the environment file to the target directory.
await createBackendEnvFile(props.root, props);
await prepareContextData(
props.root,
props.dataSources.filter((ds) => ds.type === "file"),
);
if (
props.dataSources.length > 0 &&
(props.postInstallAction === "runApp" ||
props.postInstallAction === "dependencies")
) {
console.log("\nGenerating context data...\n");
await generateContextData(
props.framework,
props.modelConfig,
props.dataSources,
props.packageManager,
props.vectorDb,
props.llamaCloudKey,
props.useLlamaParse,
);
}
// Create outputs directory
await makeDir(path.join(props.root, "output/tools"));
await makeDir(path.join(props.root, "output/uploaded"));
await makeDir(path.join(props.root, "output/llamacloud"));
};
export * from "./types";
@@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import spawn from "cross-spawn";
import { yellow } from "picocolors";
import type { PackageManager } from "./get-pkg-manager";
@@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import fs from "fs";
import path from "path";
import { blue, green } from "picocolors";
+12
View File
@@ -0,0 +1,12 @@
import { ModelConfig } from "./types";
export const getGpt41ModelConfig = (): ModelConfig => ({
provider: "openai",
apiKey: process.env.OPENAI_API_KEY,
model: "gpt-4.1",
embeddingModel: "text-embedding-3-large",
dimensions: 1536,
isConfigured(): boolean {
return !!process.env.OPENAI_API_KEY;
},
});
@@ -1,7 +1,6 @@
import ciInfo from "ci-info";
import prompts from "prompts";
import { ModelConfigParams } from ".";
import { questionHandlers, toChoice } from "../../questions";
import { questionHandlers, toChoice } from "../../questions/utils";
const MODELS = [
"claude-3-opus",
@@ -32,17 +31,9 @@ const EMBEDDING_MODELS: Record<HuggingFaceEmbeddingModelType, ModelData> = {
const DEFAULT_EMBEDDING_MODEL = Object.keys(EMBEDDING_MODELS)[0];
const DEFAULT_DIMENSIONS = Object.values(EMBEDDING_MODELS)[0].dimensions;
type AnthropicQuestionsParams = {
apiKey?: string;
askModels: boolean;
};
export async function askAnthropicQuestions({
askModels,
apiKey,
}: AnthropicQuestionsParams): Promise<ModelConfigParams> {
export async function askAnthropicQuestions(): Promise<ModelConfigParams> {
const config: ModelConfigParams = {
apiKey,
apiKey: process.env.ANTHROPIC_API_KEY,
model: DEFAULT_MODEL,
embeddingModel: DEFAULT_EMBEDDING_MODEL,
dimensions: DEFAULT_DIMENSIONS,
@@ -70,37 +61,33 @@ export async function askAnthropicQuestions({
config.apiKey = key || process.env.ANTHROPIC_API_KEY;
}
// use default model values in CI or if user should not be asked
const useDefaults = ciInfo.isCI || !askModels;
if (!useDefaults) {
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which LLM model would you like to use?",
choices: MODELS.map(toChoice),
initial: 0,
},
questionHandlers,
);
config.model = model;
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which LLM model would you like to use?",
choices: MODELS.map(toChoice),
initial: 0,
},
questionHandlers,
);
config.model = model;
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: Object.keys(EMBEDDING_MODELS).map(toChoice),
initial: 0,
},
questionHandlers,
);
config.embeddingModel = embeddingModel;
config.dimensions =
EMBEDDING_MODELS[
embeddingModel as HuggingFaceEmbeddingModelType
].dimensions;
}
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: Object.keys(EMBEDDING_MODELS).map(toChoice),
initial: 0,
},
questionHandlers,
);
config.embeddingModel = embeddingModel;
config.dimensions =
EMBEDDING_MODELS[
embeddingModel as HuggingFaceEmbeddingModelType
].dimensions;
return config;
}
@@ -0,0 +1,110 @@
import prompts from "prompts";
import { ModelConfigParams } from ".";
import { questionHandlers } from "../../questions/utils";
const ALL_AZURE_OPENAI_CHAT_MODELS: Record<string, { openAIModel: string }> = {
"gpt-35-turbo": { openAIModel: "gpt-3.5-turbo" },
"gpt-35-turbo-16k": {
openAIModel: "gpt-3.5-turbo-16k",
},
"gpt-4o": { openAIModel: "gpt-4o" },
"gpt-4o-mini": { openAIModel: "gpt-4o-mini" },
"gpt-4": { openAIModel: "gpt-4" },
"gpt-4-32k": { openAIModel: "gpt-4-32k" },
"gpt-4-turbo": {
openAIModel: "gpt-4-turbo",
},
"gpt-4-turbo-2024-04-09": {
openAIModel: "gpt-4-turbo",
},
"gpt-4-vision-preview": {
openAIModel: "gpt-4-vision-preview",
},
"gpt-4-1106-preview": {
openAIModel: "gpt-4-1106-preview",
},
"gpt-4o-2024-05-13": {
openAIModel: "gpt-4o-2024-05-13",
},
"gpt-4o-mini-2024-07-18": {
openAIModel: "gpt-4o-mini-2024-07-18",
},
};
const ALL_AZURE_OPENAI_EMBEDDING_MODELS: Record<
string,
{
dimensions: number;
openAIModel: string;
}
> = {
"text-embedding-3-small": {
dimensions: 1536,
openAIModel: "text-embedding-3-small",
},
"text-embedding-3-large": {
dimensions: 3072,
openAIModel: "text-embedding-3-large",
},
};
const DEFAULT_MODEL = "gpt-4o";
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-large";
export async function askAzureQuestions(): Promise<ModelConfigParams> {
const config: ModelConfigParams = {
apiKey: process.env.AZURE_OPENAI_KEY,
model: DEFAULT_MODEL,
embeddingModel: DEFAULT_EMBEDDING_MODEL,
dimensions: getDimensions(DEFAULT_EMBEDDING_MODEL),
isConfigured(): boolean {
// the Azure model provider can't be fully configured as endpoint and deployment names have to be configured with env variables
return false;
},
};
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which LLM model would you like to use?",
choices: getAvailableModelChoices(),
initial: 0,
},
questionHandlers,
);
config.model = model;
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: getAvailableEmbeddingModelChoices(),
initial: 0,
},
questionHandlers,
);
config.embeddingModel = embeddingModel;
config.dimensions = getDimensions(embeddingModel);
return config;
}
function getAvailableModelChoices() {
return Object.keys(ALL_AZURE_OPENAI_CHAT_MODELS).map((key) => ({
title: key,
value: key,
}));
}
function getAvailableEmbeddingModelChoices() {
return Object.keys(ALL_AZURE_OPENAI_EMBEDDING_MODELS).map((key) => ({
title: key,
value: key,
}));
}
function getDimensions(modelName: string) {
return ALL_AZURE_OPENAI_EMBEDDING_MODELS[modelName].dimensions;
}
@@ -0,0 +1,82 @@
import prompts from "prompts";
import { ModelConfigParams } from ".";
import { questionHandlers, toChoice } from "../../questions/utils";
const MODELS = [
"gemini-2.5-pro",
"gemini-2.5-flash",
"gemini-2.0-flash",
"gemini-2.0-flash-lite",
"gemini-1.5-pro-latest",
"gemini-pro",
"gemini-pro-vision",
];
type ModelData = {
dimensions: number;
};
const EMBEDDING_MODELS: Record<string, ModelData> = {
"embedding-001": { dimensions: 768 },
"text-embedding-004": { dimensions: 768 },
};
const DEFAULT_MODEL = MODELS[0];
const DEFAULT_EMBEDDING_MODEL = Object.keys(EMBEDDING_MODELS)[0];
const DEFAULT_DIMENSIONS = Object.values(EMBEDDING_MODELS)[0].dimensions;
export async function askGeminiQuestions(): Promise<ModelConfigParams> {
const config: ModelConfigParams = {
apiKey: process.env.GOOGLE_API_KEY,
model: DEFAULT_MODEL,
embeddingModel: DEFAULT_EMBEDDING_MODEL,
dimensions: DEFAULT_DIMENSIONS,
isConfigured(): boolean {
if (config.apiKey) {
return true;
}
if (process.env["GOOGLE_API_KEY"]) {
return true;
}
return false;
},
};
if (!config.apiKey) {
const { key } = await prompts(
{
type: "text",
name: "key",
message:
"Please provide your Google API key (or leave blank to use GOOGLE_API_KEY env variable):",
},
questionHandlers,
);
config.apiKey = key || process.env.GOOGLE_API_KEY;
}
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which LLM model would you like to use?",
choices: MODELS.map(toChoice),
initial: 0,
},
questionHandlers,
);
config.model = model;
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: Object.keys(EMBEDDING_MODELS).map(toChoice),
initial: 0,
},
questionHandlers,
);
config.embeddingModel = embeddingModel;
config.dimensions = EMBEDDING_MODELS[embeddingModel].dimensions;
return config;
}
@@ -0,0 +1,135 @@
import prompts from "prompts";
import { ModelConfigParams } from ".";
import { questionHandlers, toChoice } from "../../questions/utils";
import got from "got";
import ora from "ora";
import { red } from "picocolors";
const GROQ_API_URL = "https://api.groq.com/openai/v1";
async function getAvailableModelChoicesGroq(apiKey: string) {
if (!apiKey) {
throw new Error("Need Groq API key to retrieve model choices");
}
const spinner = ora("Fetching available models from Groq").start();
try {
const response = await got(`${GROQ_API_URL}/models`, {
headers: {
Authorization: `Bearer ${apiKey}`,
},
timeout: 5000,
responseType: "json",
});
const data: any = await response.body;
spinner.stop();
// Filter out the Whisper models
return data.data
.filter((model: any) => !model.id.toLowerCase().includes("whisper"))
.map((el: any) => {
return {
title: el.id,
value: el.id,
};
});
} catch (error: unknown) {
spinner.stop();
console.log(error);
if ((error as any).response?.statusCode === 401) {
console.log(
red(
"Invalid Groq API key provided! Please provide a valid key and try again!",
),
);
} else {
console.log(red("Request failed: " + error));
}
process.exit(1);
}
}
const DEFAULT_MODEL = "llama3-70b-8192";
// Use huggingface embedding models for now as Groq doesn't support embedding models
enum HuggingFaceEmbeddingModelType {
XENOVA_ALL_MINILM_L6_V2 = "all-MiniLM-L6-v2",
XENOVA_ALL_MPNET_BASE_V2 = "all-mpnet-base-v2",
}
type ModelData = {
dimensions: number;
};
const EMBEDDING_MODELS: Record<HuggingFaceEmbeddingModelType, ModelData> = {
[HuggingFaceEmbeddingModelType.XENOVA_ALL_MINILM_L6_V2]: {
dimensions: 384,
},
[HuggingFaceEmbeddingModelType.XENOVA_ALL_MPNET_BASE_V2]: {
dimensions: 768,
},
};
const DEFAULT_EMBEDDING_MODEL = Object.keys(EMBEDDING_MODELS)[0];
const DEFAULT_DIMENSIONS = Object.values(EMBEDDING_MODELS)[0].dimensions;
export async function askGroqQuestions(): Promise<ModelConfigParams> {
const config: ModelConfigParams = {
apiKey: process.env.GROQ_API_KEY,
model: DEFAULT_MODEL,
embeddingModel: DEFAULT_EMBEDDING_MODEL,
dimensions: DEFAULT_DIMENSIONS,
isConfigured(): boolean {
if (config.apiKey) {
return true;
}
if (process.env["GROQ_API_KEY"]) {
return true;
}
return false;
},
};
if (!config.apiKey) {
const { key } = await prompts(
{
type: "text",
name: "key",
message:
"Please provide your Groq API key (or leave blank to use GROQ_API_KEY env variable):",
},
questionHandlers,
);
config.apiKey = key || process.env.GROQ_API_KEY;
}
const modelChoices = await getAvailableModelChoicesGroq(config.apiKey!);
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which LLM model would you like to use?",
choices: modelChoices,
initial: 0,
},
questionHandlers,
);
config.model = model;
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: Object.keys(EMBEDDING_MODELS).map(toChoice),
initial: 0,
},
questionHandlers,
);
config.embeddingModel = embeddingModel;
config.dimensions =
EMBEDDING_MODELS[
embeddingModel as HuggingFaceEmbeddingModelType
].dimensions;
return config;
}
@@ -0,0 +1,60 @@
import prompts from "prompts";
import { ModelConfigParams } from ".";
import { questionHandlers, toChoice } from "../../questions/utils";
const MODELS = ["HuggingFaceH4/zephyr-7b-alpha"];
type ModelData = {
dimensions: number;
};
const EMBEDDING_MODELS: Record<string, ModelData> = {
"BAAI/bge-small-en-v1.5": { dimensions: 384 },
"BAAI/bge-base-en-v1.5": { dimensions: 768 },
"BAAI/bge-large-en-v1.5": { dimensions: 1024 },
"sentence-transformers/all-MiniLM-L6-v2": { dimensions: 384 },
"sentence-transformers/all-mpnet-base-v2": { dimensions: 768 },
"intfloat/multilingual-e5-large": { dimensions: 1024 },
"mixedbread-ai/mxbai-embed-large-v1": { dimensions: 1024 },
"nomic-ai/nomic-embed-text-v1.5": { dimensions: 768 },
};
const DEFAULT_MODEL = MODELS[0];
const DEFAULT_EMBEDDING_MODEL = Object.keys(EMBEDDING_MODELS)[0];
const DEFAULT_DIMENSIONS = Object.values(EMBEDDING_MODELS)[0].dimensions;
export async function askHuggingfaceQuestions(): Promise<ModelConfigParams> {
const config: ModelConfigParams = {
model: DEFAULT_MODEL,
embeddingModel: DEFAULT_EMBEDDING_MODEL,
dimensions: DEFAULT_DIMENSIONS,
isConfigured(): boolean {
return true;
},
};
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which Hugging Face model would you like to use?",
choices: MODELS.map(toChoice),
initial: 0,
},
questionHandlers,
);
config.model = model;
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: Object.keys(EMBEDDING_MODELS).map(toChoice),
initial: 0,
},
questionHandlers,
);
config.embeddingModel = embeddingModel;
config.dimensions = EMBEDDING_MODELS[embeddingModel].dimensions;
return config;
}
@@ -0,0 +1,81 @@
import prompts from "prompts";
import { questionHandlers } from "../../questions/utils";
import { ModelConfig, TemplateFramework } from "../types";
import { askAnthropicQuestions } from "./anthropic";
import { askAzureQuestions } from "./azure";
import { askGeminiQuestions } from "./gemini";
import { askGroqQuestions } from "./groq";
import { askHuggingfaceQuestions } from "./huggingface";
import { askLLMHubQuestions } from "./llmhub";
import { askMistralQuestions } from "./mistral";
import { askOllamaQuestions } from "./ollama";
import { askOpenAIQuestions } from "./openai";
export type ModelConfigQuestionsParams = {
framework?: TemplateFramework;
};
export type ModelConfigParams = Omit<ModelConfig, "provider">;
export async function askModelConfig({
framework,
}: ModelConfigQuestionsParams): Promise<ModelConfig> {
const choices = [
{ title: "OpenAI", value: "openai" },
{ title: "Groq", value: "groq" },
{ title: "Ollama", value: "ollama" },
{ title: "Anthropic", value: "anthropic" },
{ title: "Gemini", value: "gemini" },
{ title: "Mistral", value: "mistral" },
{ title: "AzureOpenAI", value: "azure-openai" },
];
if (framework === "fastapi") {
choices.push({ title: "T-Systems", value: "t-systems" });
choices.push({ title: "Huggingface", value: "huggingface" });
}
const { provider: modelProvider } = await prompts(
{
type: "select",
name: "provider",
message: "Which model provider would you like to use",
choices: choices,
initial: 0,
},
questionHandlers,
);
let modelConfig: ModelConfigParams;
switch (modelProvider) {
case "ollama":
modelConfig = await askOllamaQuestions();
break;
case "groq":
modelConfig = await askGroqQuestions();
break;
case "anthropic":
modelConfig = await askAnthropicQuestions();
break;
case "gemini":
modelConfig = await askGeminiQuestions();
break;
case "mistral":
modelConfig = await askMistralQuestions();
break;
case "azure-openai":
modelConfig = await askAzureQuestions();
break;
case "t-systems":
modelConfig = await askLLMHubQuestions();
break;
case "huggingface":
modelConfig = await askHuggingfaceQuestions();
break;
default:
modelConfig = await askOpenAIQuestions();
}
return {
...modelConfig,
provider: modelProvider,
};
}
@@ -0,0 +1,155 @@
import got from "got";
import ora from "ora";
import { red } from "picocolors";
import prompts from "prompts";
import { ModelConfigParams } from ".";
import { questionHandlers } from "../../questions/utils";
export const TSYSTEMS_LLMHUB_API_URL =
"https://llm-server.llmhub.t-systems.net/v2";
const DEFAULT_MODEL = "gpt-3.5-turbo";
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-large";
const LLMHUB_MODELS = [
"gpt-35-turbo",
"gpt-4-32k-1",
"gpt-4-32k-canada",
"gpt-4-32k-france",
"gpt-4-turbo-128k-france",
"Llama2-70b-Instruct",
"Llama-3-70B-Instruct",
"Mixtral-8x7B-Instruct-v0.1",
"mistral-large-32k-france",
"CodeLlama-2",
];
const LLMHUB_EMBEDDING_MODELS = [
"text-embedding-ada-002",
"text-embedding-ada-002-france",
"jina-embeddings-v2-base-de",
"jina-embeddings-v2-base-code",
"text-embedding-bge-m3",
];
export async function askLLMHubQuestions(): Promise<ModelConfigParams> {
const config: ModelConfigParams = {
apiKey: process.env.T_SYSTEMS_LLMHUB_API_KEY,
model: DEFAULT_MODEL,
embeddingModel: DEFAULT_EMBEDDING_MODEL,
dimensions: getDimensions(DEFAULT_EMBEDDING_MODEL),
isConfigured(): boolean {
if (config.apiKey) {
return true;
}
if (process.env["T_SYSTEMS_LLMHUB_API_KEY"]) {
return true;
}
return false;
},
};
if (!config.apiKey) {
const { key } = await prompts(
{
type: "text",
name: "key",
message:
"Please provide your LLMHub API key (or leave blank to use T_SYSTEMS_LLMHUB_API_KEY env variable):",
validate: (value: string) => {
if (!value) {
if (process.env.T_SYSTEMS_LLMHUB_API_KEY) {
return true;
}
return "T_SYSTEMS_LLMHUB_API_KEY env variable is not set - key is required";
}
return true;
},
},
questionHandlers,
);
config.apiKey = key || process.env.T_SYSTEMS_LLMHUB_API_KEY;
}
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which LLM model would you like to use?",
choices: await getAvailableModelChoices(false, config.apiKey),
initial: 0,
},
questionHandlers,
);
config.model = model;
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: await getAvailableModelChoices(true, config.apiKey),
initial: 0,
},
questionHandlers,
);
config.embeddingModel = embeddingModel;
config.dimensions = getDimensions(embeddingModel);
return config;
}
async function getAvailableModelChoices(
selectEmbedding: boolean,
apiKey?: string,
) {
if (!apiKey) {
throw new Error("Need LLMHub key to retrieve model choices");
}
const isLLMModel = (modelId: string) => {
return LLMHUB_MODELS.includes(modelId);
};
const isEmbeddingModel = (modelId: string) => {
return LLMHUB_EMBEDDING_MODELS.includes(modelId);
};
const spinner = ora("Fetching available models").start();
try {
const response = await got(`${TSYSTEMS_LLMHUB_API_URL}/models`, {
headers: {
Authorization: "Bearer " + apiKey,
},
timeout: 5000,
responseType: "json",
});
const data: any = await response.body;
spinner.stop();
return data.data
.filter((model: any) =>
selectEmbedding ? isEmbeddingModel(model.id) : isLLMModel(model.id),
)
.map((el: any) => {
return {
title: el.id,
value: el.id,
};
});
} catch (error) {
spinner.stop();
if ((error as any).response?.statusCode === 401) {
console.log(
red(
"Invalid LLMHub API key provided! Please provide a valid key and try again!",
),
);
} else {
console.log(red("Request failed: " + error));
}
process.exit(1);
}
}
function getDimensions(modelName: string) {
// Assuming dimensions similar to OpenAI for simplicity. Update if different.
return modelName === "text-embedding-004" ? 768 : 1536;
}
@@ -0,0 +1,73 @@
import prompts from "prompts";
import { ModelConfigParams } from ".";
import { questionHandlers, toChoice } from "../../questions/utils";
const MODELS = ["mistral-tiny", "mistral-small", "mistral-medium"];
type ModelData = {
dimensions: number;
};
const EMBEDDING_MODELS: Record<string, ModelData> = {
"mistral-embed": { dimensions: 1024 },
};
const DEFAULT_MODEL = MODELS[0];
const DEFAULT_EMBEDDING_MODEL = Object.keys(EMBEDDING_MODELS)[0];
const DEFAULT_DIMENSIONS = Object.values(EMBEDDING_MODELS)[0].dimensions;
export async function askMistralQuestions(): Promise<ModelConfigParams> {
const config: ModelConfigParams = {
apiKey: process.env.MISTRAL_API_KEY,
model: DEFAULT_MODEL,
embeddingModel: DEFAULT_EMBEDDING_MODEL,
dimensions: DEFAULT_DIMENSIONS,
isConfigured(): boolean {
if (config.apiKey) {
return true;
}
if (process.env["MISTRAL_API_KEY"]) {
return true;
}
return false;
},
};
if (!config.apiKey) {
const { key } = await prompts(
{
type: "text",
name: "key",
message:
"Please provide your Mistral API key (or leave blank to use MISTRAL_API_KEY env variable):",
},
questionHandlers,
);
config.apiKey = key || process.env.MISTRAL_API_KEY;
}
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which LLM model would you like to use?",
choices: MODELS.map(toChoice),
initial: 0,
},
questionHandlers,
);
config.model = model;
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: Object.keys(EMBEDDING_MODELS).map(toChoice),
initial: 0,
},
questionHandlers,
);
config.embeddingModel = embeddingModel;
config.dimensions = EMBEDDING_MODELS[embeddingModel].dimensions;
return config;
}
@@ -1,9 +1,8 @@
import ciInfo from "ci-info";
import ollama, { type ModelResponse } from "ollama";
import { red } from "picocolors";
import prompts from "prompts";
import { ModelConfigParams } from ".";
import { questionHandlers, toChoice } from "../../questions";
import { questionHandlers, toChoice } from "../../questions/utils";
type ModelData = {
dimensions: number;
@@ -18,13 +17,7 @@ const EMBEDDING_MODELS: Record<string, ModelData> = {
};
const DEFAULT_EMBEDDING_MODEL: string = Object.keys(EMBEDDING_MODELS)[0];
type OllamaQuestionsParams = {
askModels: boolean;
};
export async function askOllamaQuestions({
askModels,
}: OllamaQuestionsParams): Promise<ModelConfigParams> {
export async function askOllamaQuestions(): Promise<ModelConfigParams> {
const config: ModelConfigParams = {
model: DEFAULT_MODEL,
embeddingModel: DEFAULT_EMBEDDING_MODEL,
@@ -34,36 +27,32 @@ export async function askOllamaQuestions({
},
};
// use default model values in CI or if user should not be asked
const useDefaults = ciInfo.isCI || !askModels;
if (!useDefaults) {
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which LLM model would you like to use?",
choices: MODELS.map(toChoice),
initial: 0,
},
questionHandlers,
);
await ensureModel(model);
config.model = model;
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which LLM model would you like to use?",
choices: MODELS.map(toChoice),
initial: 0,
},
questionHandlers,
);
await ensureModel(model);
config.model = model;
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: Object.keys(EMBEDDING_MODELS).map(toChoice),
initial: 0,
},
questionHandlers,
);
await ensureModel(embeddingModel);
config.embeddingModel = embeddingModel;
config.dimensions = EMBEDDING_MODELS[embeddingModel].dimensions;
}
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: Object.keys(EMBEDDING_MODELS).map(toChoice),
initial: 0,
},
questionHandlers,
);
await ensureModel(embeddingModel);
config.embeddingModel = embeddingModel;
config.dimensions = EMBEDDING_MODELS[embeddingModel].dimensions;
return config;
}
@@ -1,22 +1,18 @@
import ciInfo from "ci-info";
import got from "got";
import ora from "ora";
import { red } from "picocolors";
import prompts from "prompts";
import { ModelConfigParams, ModelConfigQuestionsParams } from ".";
import { questionHandlers } from "../../questions";
import { ModelConfigParams } from ".";
import { questionHandlers } from "../../questions/utils";
const OPENAI_API_URL = "https://api.openai.com/v1";
const DEFAULT_MODEL = "gpt-3.5-turbo";
const DEFAULT_MODEL = "gpt-4o-mini";
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-large";
export async function askOpenAIQuestions({
openAiKey,
askModels,
}: ModelConfigQuestionsParams): Promise<ModelConfigParams> {
export async function askOpenAIQuestions(): Promise<ModelConfigParams> {
const config: ModelConfigParams = {
apiKey: openAiKey,
apiKey: process.env.OPENAI_API_KEY,
model: DEFAULT_MODEL,
embeddingModel: DEFAULT_EMBEDDING_MODEL,
dimensions: getDimensions(DEFAULT_EMBEDDING_MODEL),
@@ -36,11 +32,10 @@ export async function askOpenAIQuestions({
{
type: "text",
name: "key",
message: askModels
? "Please provide your OpenAI API key (or leave blank to use OPENAI_API_KEY env variable):"
: "Please provide your OpenAI API key (leave blank to skip):",
message:
"Please provide your OpenAI API key (or leave blank to use OPENAI_API_KEY env variable):",
validate: (value: string) => {
if (askModels && !value) {
if (!value) {
if (process.env.OPENAI_API_KEY) {
return true;
}
@@ -54,34 +49,30 @@ export async function askOpenAIQuestions({
config.apiKey = key || process.env.OPENAI_API_KEY;
}
// use default model values in CI or if user should not be asked
const useDefaults = ciInfo.isCI || !askModels;
if (!useDefaults) {
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which LLM model would you like to use?",
choices: await getAvailableModelChoices(false, config.apiKey),
initial: 0,
},
questionHandlers,
);
config.model = model;
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which LLM model would you like to use?",
choices: await getAvailableModelChoices(false, config.apiKey),
initial: 0,
},
questionHandlers,
);
config.model = model;
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: await getAvailableModelChoices(true, config.apiKey),
initial: 0,
},
questionHandlers,
);
config.embeddingModel = embeddingModel;
config.dimensions = getDimensions(embeddingModel);
}
const { embeddingModel } = await prompts(
{
type: "select",
name: "embeddingModel",
message: "Which embedding model would you like to use?",
choices: await getAvailableModelChoices(true, config.apiKey),
initial: 0,
},
questionHandlers,
);
config.embeddingModel = embeddingModel;
config.dimensions = getDimensions(embeddingModel);
return config;
}
+531
View File
@@ -0,0 +1,531 @@
import fs from "fs/promises";
import path from "path";
import { cyan, red } from "picocolors";
import { parse, stringify } from "smol-toml";
import terminalLink from "terminal-link";
import { isUvAvailable, tryUvSync } from "./uv";
import { assetRelocator, copy } from "./copy";
import { templatesDir } from "./dir";
import {
InstallTemplateArgs,
ModelConfig,
TemplateDataSource,
TemplateVectorDB,
} from "./types";
interface Dependency {
name: string;
version?: string;
extras?: string[];
constraints?: Record<string, string>;
}
const getAdditionalDependencies = (
modelConfig: ModelConfig,
vectorDb?: TemplateVectorDB,
dataSources?: TemplateDataSource[],
) => {
const dependencies: Dependency[] = [];
// Add vector db dependencies
switch (vectorDb) {
case "mongo": {
dependencies.push({
name: "llama-index-vector-stores-mongodb",
version: ">=0.3.2,<0.4.0",
});
break;
}
case "pg": {
dependencies.push({
name: "llama-index-vector-stores-postgres",
version: ">=0.3.2,<0.4.0",
});
break;
}
case "pinecone": {
dependencies.push({
name: "llama-index-vector-stores-pinecone",
version: ">=0.4.1,<0.5.0",
constraints: {
python: ">=3.11,<3.13",
},
});
break;
}
case "milvus": {
dependencies.push({
name: "llama-index-vector-stores-milvus",
version: ">=0.3.0,<0.4.0",
});
dependencies.push({
name: "pymilvus",
version: ">=2.4.4,<3.0.0",
});
break;
}
case "astra": {
dependencies.push({
name: "llama-index-vector-stores-astra-db",
version: ">=0.4.0,<0.5.0",
});
break;
}
case "qdrant": {
dependencies.push({
name: "llama-index-vector-stores-qdrant",
version: ">=0.4.0,<0.5.0",
constraints: {
python: ">=3.11,<3.13",
},
});
break;
}
case "chroma": {
dependencies.push({
name: "llama-index-vector-stores-chroma",
version: ">=0.4.0,<0.5.0",
});
dependencies.push({
name: "onnxruntime",
version: "<1.22.0",
});
break;
}
case "weaviate": {
dependencies.push({
name: "llama-index-vector-stores-weaviate",
version: ">=1.2.3,<2.0.0",
});
break;
}
case "llamacloud":
dependencies.push({
name: "llama-index-indices-managed-llama-cloud",
version: ">=0.6.3,<0.7.0",
});
break;
}
// Add data source dependencies
if (dataSources) {
for (const ds of dataSources) {
const dsType = ds?.type;
switch (dsType) {
case "file":
dependencies.push({
name: "docx2txt",
version: ">=0.8,<0.9",
});
break;
case "web":
dependencies.push({
name: "llama-index-readers-web",
version: ">=0.3.0,<0.4.0",
});
break;
case "db":
dependencies.push({
name: "llama-index-readers-database",
version: ">=0.3.0,<0.4.0",
});
dependencies.push({
name: "pymysql",
version: ">=1.1.0,<2.0.0",
extras: ["rsa"],
});
dependencies.push({
name: "psycopg2-binary",
version: ">=2.9.9,<3.0.0",
});
break;
}
}
}
switch (modelConfig.provider) {
case "ollama":
dependencies.push({
name: "llama-index-llms-ollama",
version: ">=0.5.0,<0.6.0",
});
dependencies.push({
name: "llama-index-embeddings-ollama",
version: ">=0.6.0,<0.7.0",
});
break;
case "openai":
dependencies.push({
name: "llama-index-llms-openai",
version: ">=0.3.2,<0.4.0",
});
dependencies.push({
name: "llama-index-embeddings-openai",
version: ">=0.3.1,<0.4.0",
});
break;
case "groq":
dependencies.push({
name: "llama-index-llms-groq",
version: ">=0.3.0,<0.4.0",
});
dependencies.push({
name: "llama-index-embeddings-fastembed",
version: ">=0.3.0,<0.4.0",
});
break;
case "anthropic":
dependencies.push({
name: "llama-index-llms-anthropic",
version: ">=0.6.0,<0.7.0",
});
dependencies.push({
name: "llama-index-embeddings-fastembed",
version: ">=0.3.0,<0.4.0",
});
break;
case "gemini":
dependencies.push({
name: "llama-index-llms-google-genai",
version: ">=0.2.0,<0.3.0",
});
dependencies.push({
name: "llama-index-embeddings-google-genai",
version: ">=0.2.0,<0.3.0",
});
break;
case "mistral":
dependencies.push({
name: "llama-index-llms-mistralai",
version: ">=0.4.0,<0.5.0",
});
dependencies.push({
name: "llama-index-embeddings-mistralai",
version: ">=0.3.0,<0.4.0",
});
break;
case "azure-openai":
dependencies.push({
name: "llama-index-llms-azure-openai",
version: ">=0.3.0,<0.4.0",
});
dependencies.push({
name: "llama-index-embeddings-azure-openai",
version: ">=0.3.0,<0.4.0",
});
break;
case "huggingface":
dependencies.push({
name: "llama-index-llms-huggingface",
version: ">=0.5.0,<0.6.0",
});
dependencies.push({
name: "llama-index-embeddings-huggingface",
version: ">=0.5.0,<0.6.0",
});
dependencies.push({
name: "optimum",
version: ">=1.23.3,<2.0.0",
extras: ["onnxruntime"],
});
break;
case "t-systems":
dependencies.push({
name: "llama-index-agent-openai",
version: ">=0.4.0,<0.5.0",
});
dependencies.push({
name: "llama-index-llms-openai-like",
version: ">=0.3.0,<0.4.0",
});
break;
}
// If app template is llama-index-server and CI and SERVER_PACKAGE_PATH is set,
// add @llamaindex/server to dependencies
if (process.env.SERVER_PACKAGE_PATH) {
dependencies.push({
name: "llama-index-server",
version: `@file://${process.env.SERVER_PACKAGE_PATH}`,
});
}
return dependencies;
};
export const addDependencies = async (
projectDir: string,
dependencies: Dependency[],
) => {
if (dependencies.length === 0) return;
const FILENAME = "pyproject.toml";
try {
// Parse toml file
const file = path.join(projectDir, FILENAME);
const fileContent = await fs.readFile(file, "utf8");
let fileParsed: any;
try {
fileParsed = parse(fileContent);
} catch (parseError) {
console.error(`Error parsing ${FILENAME}:`, parseError);
throw new Error(
`Failed to parse ${FILENAME}. Please ensure it's valid TOML.`,
);
}
// Ensure [project] and [project.dependencies] exist
if (!fileParsed.project) {
fileParsed.project = {};
}
if (
!fileParsed.project.dependencies ||
!Array.isArray(fileParsed.project.dependencies)
) {
// If dependencies exist but aren't an array, log a warning or error.
// For now, we'll overwrite it, assuming the intent is to use the standard array format.
console.warn(
`[project.dependencies] in ${FILENAME} is not an array. It will be overwritten.`,
);
fileParsed.project.dependencies = [];
}
const existingDependencies: string[] = fileParsed.project.dependencies;
const addedDeps: string[] = [];
const updatedDeps: string[] = [];
// Add or update dependencies
for (const newDep of dependencies) {
let depString = newDep.name;
if (newDep.extras && newDep.extras.length > 0) {
depString += `[${newDep.extras.join(",")}]`;
}
if (newDep.version) {
depString += newDep.version;
}
let found = false;
for (let i = 0; i < existingDependencies.length; i++) {
const existingDepNameMatch =
existingDependencies[i].match(/^([a-zA-Z0-9._-]+)/);
if (
existingDepNameMatch &&
existingDepNameMatch[1].toLowerCase() === depString.toLowerCase()
) {
// Found existing dependency, update it
if (existingDependencies[i] !== depString) {
updatedDeps.push(`${existingDependencies[i]} -> ${depString}`);
existingDependencies[i] = depString;
}
found = true;
break;
}
}
if (!found) {
// Add new dependency
existingDependencies.push(depString);
addedDeps.push(depString);
}
// Handle python version constraints separately (if any)
if (newDep.constraints?.python) {
if (
!fileParsed.project["requires-python"] ||
fileParsed.project["requires-python"] !== newDep.constraints.python
) {
// This simple overwrite might not be ideal; merging constraints is complex.
// For now, let's just set it if the new dependency has one.
console.log(
`Setting requires-python = "${newDep.constraints.python}" from dependency ${newDep.name}`,
);
fileParsed.project["requires-python"] = newDep.constraints.python;
}
}
}
// Write toml file
const newFileContent = stringify(fileParsed);
await fs.writeFile(file, newFileContent);
if (addedDeps.length > 0) {
console.log(`\nAdded dependencies to ${cyan(FILENAME)}:`);
addedDeps.forEach((dep) => console.log(` ${dep}`));
}
if (updatedDeps.length > 0) {
console.log(`\nUpdated dependencies in ${cyan(FILENAME)}:`);
updatedDeps.forEach((dep) => console.log(` ${dep}`));
}
if (addedDeps.length > 0 || updatedDeps.length > 0) {
console.log(""); // Newline for spacing
}
} catch (error) {
console.log(
`Error while updating dependencies for Poetry project file ${FILENAME}\n`,
error,
);
}
};
export const installPythonDependencies = () => {
if (isUvAvailable()) {
console.log(
`Installing Python dependencies using uv. This may take a while...`,
);
const installSuccessful = tryUvSync();
if (!installSuccessful) {
console.error(
red(
"Installing dependencies using uv failed. Please check the error log above and ensure uv is installed correctly.",
),
);
process.exit(1);
}
} else {
console.error(
red(
`uv is not available in the current environment. Please check ${terminalLink(
"uv Installation",
`https://github.com/astral-sh/uv#installation`,
)} to install uv first, then run create-llama again.`,
),
);
process.exit(1);
}
};
const installLlamaIndexServerTemplate = async ({
root,
useCase,
useLlamaParse,
modelConfig,
}: Pick<
InstallTemplateArgs,
"root" | "useCase" | "useLlamaParse" | "modelConfig"
>) => {
if (!useCase) {
console.log(
red(
`There is no use case selected. Please pick a use case to use via --use-case flag.`,
),
);
process.exit(1);
}
await copy("*.py", path.join(root, "app"), {
parents: true,
cwd: path.join(templatesDir, "components", "use-cases", "python", useCase),
});
// copy model provider settings to app folder
await copy("**", path.join(root, "app"), {
cwd: path.join(
templatesDir,
"components",
"providers",
"python",
modelConfig.provider,
),
});
// Copy custom UI component code
await copy(`*`, path.join(root, "components"), {
parents: true,
cwd: path.join(templatesDir, "components", "ui", "use-cases", useCase),
});
// Copy layout components to layout folder in root
await copy("*", path.join(root, "layout"), {
parents: true,
cwd: path.join(templatesDir, "components", "ui", "layout"),
});
if (useLlamaParse) {
await copy("index.py", path.join(root, "app"), {
parents: true,
cwd: path.join(
templatesDir,
"components",
"vectordbs",
"llamaindexserver",
"llamacloud",
"python",
),
});
// TODO: Consider moving generate.py to app folder.
await copy("generate.py", path.join(root), {
parents: true,
cwd: path.join(
templatesDir,
"components",
"vectordbs",
"llamaindexserver",
"llamacloud",
"python",
),
});
}
// Copy README.md
await copy("README-template.md", path.join(root), {
parents: true,
cwd: path.join(templatesDir, "components", "use-cases", "python", useCase),
rename: assetRelocator,
});
};
export const installPythonTemplate = async ({
appName,
root,
template,
framework,
vectorDb,
postInstallAction,
modelConfig,
dataSources,
useLlamaParse,
useCase,
}: Pick<
InstallTemplateArgs,
| "appName"
| "root"
| "template"
| "framework"
| "vectorDb"
| "postInstallAction"
| "modelConfig"
| "dataSources"
| "useLlamaParse"
| "useCase"
>) => {
console.log("\nInitializing Python project with template:", template, "\n");
const templatePath = path.join(templatesDir, "types", template, framework);
await copy("**", root, {
parents: true,
cwd: templatePath,
rename: assetRelocator,
});
if (template === "llamaindexserver") {
await installLlamaIndexServerTemplate({
root,
useCase,
useLlamaParse,
modelConfig,
});
} else {
throw new Error(`Template ${template} not supported`);
}
console.log("Adding additional dependencies");
const addOnDependencies = getAdditionalDependencies(
modelConfig,
vectorDb,
dataSources,
);
await addDependencies(root, addOnDependencies);
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
installPythonDependencies();
}
};
+66
View File
@@ -0,0 +1,66 @@
import { SpawnOptions, spawn } from "child_process";
import { TemplateFramework, TemplateType } from "./types";
const createProcess = (
command: string,
args: string[],
options: SpawnOptions,
): Promise<void> => {
return new Promise((resolve, reject) => {
spawn(command, args, {
...options,
shell: true,
})
.on("exit", function (code) {
if (code !== 0) {
console.log(`Child process exited with code=${code}`);
reject(code);
} else {
resolve();
}
})
.on("error", function (err) {
console.log("Error when running child process: ", err);
reject(err);
});
});
};
export function runFastAPIApp(
appPath: string,
port: number,
template: TemplateType,
) {
const commandArgs = ["run", "fastapi", "dev", "--port", `${port}`];
return createProcess("uv", commandArgs, {
stdio: "inherit",
cwd: appPath,
env: { ...process.env, APP_PORT: `${port}` },
});
}
export function runTSApp(appPath: string, port: number) {
return createProcess("npm", ["run", "dev"], {
stdio: "inherit",
cwd: appPath,
env: { ...process.env, PORT: `${port}` },
});
}
export async function runApp(
appPath: string,
template: TemplateType,
framework: TemplateFramework,
port?: number,
): Promise<void> {
try {
// Start the app
const defaultPort = framework === "nextjs" ? 3000 : 8000;
const appRunner = framework === "fastapi" ? runFastAPIApp : runTSApp;
await appRunner(appPath, port || defaultPort, template);
} catch (error) {
console.error("Failed to run app:", error);
throw error;
}
}
@@ -1,7 +1,15 @@
import { PackageManager } from "../helpers/get-pkg-manager";
import { Tool } from "./tools";
export type ModelProvider = "openai" | "ollama" | "anthropic" | "gemini";
export type ModelProvider =
| "openai"
| "groq"
| "ollama"
| "anthropic"
| "gemini"
| "mistral"
| "azure-openai"
| "huggingface"
| "t-systems";
export type ModelConfig = {
provider: ModelProvider;
apiKey?: string;
@@ -10,9 +18,8 @@ export type ModelConfig = {
dimensions: number;
isConfigured(): boolean;
};
export type TemplateType = "streaming" | "community" | "llamapack";
export type TemplateType = "llamaindexserver";
export type TemplateFramework = "nextjs" | "express" | "fastapi";
export type TemplateUI = "html" | "shadcn";
export type TemplateVectorDB =
| "none"
| "mongo"
@@ -21,7 +28,9 @@ export type TemplateVectorDB =
| "milvus"
| "astra"
| "qdrant"
| "chroma";
| "chroma"
| "llamacloud"
| "weaviate";
export type TemplatePostInstallAction =
| "none"
| "VSCode"
@@ -32,11 +41,32 @@ export type TemplateDataSource = {
config: TemplateDataSourceConfig;
};
export type TemplateDataSourceType = "file" | "web" | "db";
export type TemplateObservability = "none" | "opentelemetry";
export type TemplateUseCase =
| "financial_report"
| "deep_research"
| "agentic_rag"
| "code_generator"
| "document_generator"
| "hitl";
export const ALL_USE_CASES: TemplateUseCase[] = [
"agentic_rag",
"deep_research",
"financial_report",
"code_generator",
"document_generator",
"hitl",
];
// Config for both file and folder
export type FileSourceConfig = {
path: string;
};
export type FileSourceConfig =
| {
path: string;
filename?: string;
}
| {
url: URL;
filename?: string;
};
export type WebSourceConfig = {
baseUrl?: string;
prefix?: string;
@@ -52,31 +82,18 @@ export type TemplateDataSourceConfig =
| WebSourceConfig
| DbSourceConfig;
export type CommunityProjectConfig = {
owner: string;
repo: string;
branch: string;
filePath?: string;
};
export interface InstallTemplateArgs {
appName: string;
root: string;
packageManager: PackageManager;
isOnline: boolean;
template: TemplateType;
framework: TemplateFramework;
ui: TemplateUI;
dataSources: TemplateDataSource[];
customApiPath?: string;
modelConfig: ModelConfig;
llamaCloudKey?: string;
useLlamaParse?: boolean;
communityProjectConfig?: CommunityProjectConfig;
llamapack?: string;
vectorDb?: TemplateVectorDB;
externalPort?: number;
postInstallAction?: TemplatePostInstallAction;
tools?: Tool[];
observability?: TemplateObservability;
useLlamaParse: boolean;
vectorDb: TemplateVectorDB;
port?: number;
postInstallAction: TemplatePostInstallAction;
useCase: TemplateUseCase;
}
+286
View File
@@ -0,0 +1,286 @@
import fs from "fs/promises";
import os from "os";
import path from "path";
import { bold, cyan, red } from "picocolors";
import { assetRelocator, copy } from "../helpers/copy";
import { callPackageManager } from "../helpers/install";
import { templatesDir } from "./dir";
import { PackageManager } from "./get-pkg-manager";
import { InstallTemplateArgs, ModelProvider, TemplateVectorDB } from "./types";
const installLlamaIndexServerTemplate = async ({
root,
useCase,
vectorDb,
modelConfig,
dataSources,
}: Pick<
InstallTemplateArgs,
"root" | "useCase" | "vectorDb" | "modelConfig" | "dataSources"
>) => {
if (!useCase) {
console.log(
red(
`There is no use case selected. Please pick a use case to use via --use-case flag.`,
),
);
process.exit(1);
}
if (!vectorDb) {
console.log(
red(
`There is no vector db selected. Please pick a vector db to use via --vector-db flag.`,
),
);
process.exit(1);
}
// copy model provider settings to app folder
await copy("**", path.join(root, "src", "app"), {
cwd: path.join(
templatesDir,
"components",
"providers",
"typescript",
modelConfig.provider,
),
});
await copy("**", path.join(root), {
cwd: path.join(
templatesDir,
"components",
"use-cases",
"typescript",
useCase,
),
rename: assetRelocator,
});
// copy workflow UI components to components folder in root
await copy("*", path.join(root, "components"), {
parents: true,
cwd: path.join(templatesDir, "components", "ui", "use-cases", useCase),
});
// copy layout components to layout folder in root
await copy("*", path.join(root, "layout"), {
parents: true,
cwd: path.join(templatesDir, "components", "ui", "layout"),
});
// Override generate.ts if workflow use case doesn't use custom UI
if (vectorDb === "llamacloud") {
await copy("**", path.join(root, "src"), {
parents: true,
cwd: path.join(
templatesDir,
"components",
"vectordbs",
"llamaindexserver",
"llamacloud",
"typescript",
),
});
}
// Simplify use case code
if (vectorDb === "none" && dataSources.length === 0) {
// use case without data sources doesn't use index.
// We don't need data.ts, generate.ts
await fs.rm(path.join(root, "src", "app", "data.ts"));
// TODO: split generate.ts into generate for index and generate for ui and remove generate for index here too
// then we can also remove it for llamacloud
}
};
/**
* Install a LlamaIndex internal template to a given `root` directory.
*/
export const installTSTemplate = async ({
appName,
root,
packageManager,
template,
framework,
vectorDb,
postInstallAction,
dataSources,
useCase,
modelConfig,
}: InstallTemplateArgs) => {
console.log(bold(`Using ${packageManager}.`));
/**
* Copy the template files to the target directory.
*/
console.log("\nInitializing project with template:", template, "\n");
const templatePath = path.join(templatesDir, "types", template, framework);
const copySource = ["**"];
await copy(copySource, root, {
parents: true,
cwd: templatePath,
rename: assetRelocator,
});
if (template === "llamaindexserver") {
await installLlamaIndexServerTemplate({
root,
useCase,
vectorDb,
modelConfig,
dataSources,
});
} else {
throw new Error(`Template ${template} not supported`);
}
const packageJson = await updatePackageJson({
root,
appName,
vectorDb,
modelConfig,
});
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
await installTSDependencies(packageJson, packageManager, true);
}
};
const providerDependencies: {
[key in ModelProvider]?: Record<string, string>;
} = {
openai: {
"@llamaindex/openai": "~0.4.0",
},
gemini: {
"@llamaindex/google": "^0.2.0",
},
ollama: {
"@llamaindex/ollama": "^0.1.0",
},
mistral: {
"@llamaindex/mistral": "^0.2.0",
},
"azure-openai": {
"@llamaindex/openai": "^0.2.0",
},
groq: {
"@llamaindex/groq": "^0.0.61",
"@llamaindex/huggingface": "^0.1.0", // groq uses huggingface as default embedding model
},
anthropic: {
"@llamaindex/anthropic": "^0.3.0",
"@llamaindex/huggingface": "^0.1.0", // anthropic uses huggingface as default embedding model
},
};
const vectorDbDependencies: Record<TemplateVectorDB, Record<string, string>> = {
astra: {
"@llamaindex/astra": "^0.0.5",
},
chroma: {
"@llamaindex/chroma": "^0.0.5",
},
llamacloud: {},
milvus: {
"@zilliz/milvus2-sdk-node": "^2.4.6",
"@llamaindex/milvus": "^0.1.0",
},
mongo: {
mongodb: "6.7.0",
"@llamaindex/mongodb": "^0.0.5",
},
none: {},
pg: {
pg: "^8.12.0",
pgvector: "^0.2.0",
"@llamaindex/postgres": "^0.0.33",
},
pinecone: {
"@llamaindex/pinecone": "^0.0.5",
},
qdrant: {
"@qdrant/js-client-rest": "^1.11.0",
"@llamaindex/qdrant": "^0.1.0",
},
weaviate: {
"@llamaindex/weaviate": "^0.0.5",
},
};
async function updatePackageJson({
root,
appName,
vectorDb,
modelConfig,
}: Pick<
InstallTemplateArgs,
"root" | "appName" | "vectorDb" | "modelConfig"
>): Promise<any> {
const packageJsonFile = path.join(root, "package.json");
const packageJson: any = JSON.parse(
await fs.readFile(packageJsonFile, "utf8"),
);
packageJson.name = appName;
packageJson.version = "0.1.0";
packageJson.dependencies = {
...packageJson.dependencies,
"@llamaindex/readers": "~3.1.4",
};
if (vectorDb && vectorDb in vectorDbDependencies) {
packageJson.dependencies = {
...packageJson.dependencies,
...vectorDbDependencies[vectorDb],
};
}
if (modelConfig.provider && modelConfig.provider in providerDependencies) {
packageJson.dependencies = {
...packageJson.dependencies,
...providerDependencies[modelConfig.provider],
};
}
// if having custom server package tgz file, use it for testing @llamaindex/server
const serverPackagePath = process.env.SERVER_PACKAGE_PATH;
if (serverPackagePath) {
const relativePath = path.relative(process.cwd(), serverPackagePath);
packageJson.dependencies = {
...packageJson.dependencies,
"@llamaindex/server": `file:${relativePath}`,
};
}
await fs.writeFile(
packageJsonFile,
JSON.stringify(packageJson, null, 2) + os.EOL,
);
return packageJson;
}
async function installTSDependencies(
packageJson: any,
packageManager: PackageManager,
isOnline: boolean,
): Promise<void> {
console.log("\nInstalling dependencies:");
for (const dependency in packageJson.dependencies)
console.log(`- ${cyan(dependency)}`);
console.log("\nInstalling devDependencies:");
for (const dependency in packageJson.devDependencies)
console.log(`- ${cyan(dependency)}`);
console.log();
await callPackageManager(packageManager, isOnline).catch((error) => {
console.error("Failed to install TS dependencies. Exiting...");
process.exit(1);
});
}
+42
View File
@@ -0,0 +1,42 @@
// Migrate poetry to uv
import { execSync } from "child_process";
import fs from "fs";
import { red } from "picocolors";
export function isUvAvailable(): boolean {
try {
execSync("uv --version", { stdio: "ignore" });
return true;
} catch (_) {}
return false;
}
export function tryUvSync(): boolean {
try {
console.log("Syncing environment with pyproject.toml...");
execSync(`uv sync`, {
stdio: "inherit",
});
return true;
} catch (_) {}
return false;
}
export function tryUvRun(command: string): boolean {
try {
// Use uv run <command>
execSync(`uv run ${command}`, { stdio: "inherit" });
return true;
} catch (error) {
console.error(red(`Failed to run ${command}. Error: ${error}`));
return false;
}
}
export function isHavingUvLockFile(): boolean {
try {
// Check if uv.lock exists in the current directory
return fs.existsSync("uv.lock");
} catch (_) {}
return false;
}
@@ -1,4 +1,3 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import validateProjectName from "validate-npm-package-name";
export function validateNpmName(name: string): {
@@ -1,40 +1,26 @@
import fs from "fs";
import path from "path";
import { assetRelocator, copy } from "./copy";
import { TemplateFramework } from "./types";
function renderDevcontainerContent(
templatesDir: string,
framework: TemplateFramework,
frontend: boolean,
) {
const devcontainerJson: any = JSON.parse(
fs.readFileSync(path.join(templatesDir, "devcontainer.json"), "utf8"),
);
// Modify postCreateCommand
if (frontend) {
devcontainerJson.postCreateCommand =
framework === "fastapi"
? "cd backend && poetry install && cd ../frontend && npm install"
: "cd backend && npm install && cd ../frontend && npm install";
} else {
devcontainerJson.postCreateCommand =
framework === "fastapi" ? "poetry install" : "npm install";
}
devcontainerJson.postCreateCommand =
framework === "fastapi" ? "poetry install" : "npm install";
// Modify containerEnv
if (framework === "fastapi") {
if (frontend) {
devcontainerJson.containerEnv = {
...devcontainerJson.containerEnv,
PYTHONPATH: "${PYTHONPATH}:${workspaceFolder}/backend",
};
} else {
devcontainerJson.containerEnv = {
...devcontainerJson.containerEnv,
PYTHONPATH: "${PYTHONPATH}:${workspaceFolder}",
};
}
devcontainerJson.containerEnv = {
...devcontainerJson.containerEnv,
PYTHONPATH: "${PYTHONPATH}:${workspaceFolder}",
};
}
return JSON.stringify(devcontainerJson, null, 2);
@@ -44,7 +30,6 @@ export const writeDevcontainer = async (
root: string,
templatesDir: string,
framework: TemplateFramework,
frontend: boolean,
) => {
const devcontainerDir = path.join(root, ".devcontainer");
if (fs.existsSync(devcontainerDir)) {
@@ -54,7 +39,6 @@ export const writeDevcontainer = async (
const devcontainerContent = renderDevcontainerContent(
templatesDir,
framework,
frontend,
);
fs.mkdirSync(devcontainerDir);
await fs.promises.writeFile(
@@ -62,3 +46,25 @@ export const writeDevcontainer = async (
devcontainerContent,
);
};
export const copyVSCodeSettings = async (
root: string,
templatesDir: string,
) => {
const vscodeDir = path.join(root, ".vscode");
await copy("vscode_settings.json", vscodeDir, {
cwd: templatesDir,
rename: assetRelocator,
});
};
export const configVSCode = async (
root: string,
templatesDir: string,
framework: TemplateFramework,
) => {
await writeDevcontainer(root, templatesDir, framework);
if (framework === "fastapi") {
await copyVSCodeSettings(root, templatesDir);
}
};
+33 -150
View File
@@ -1,7 +1,5 @@
/* eslint-disable import/no-extraneous-dependencies */
import { execSync } from "child_process";
import Commander from "commander";
import Conf from "conf";
import { Command } from "commander";
import fs from "fs";
import path from "path";
import { bold, cyan, green, red, yellow } from "picocolors";
@@ -9,16 +7,15 @@ import prompts from "prompts";
import terminalLink from "terminal-link";
import checkForUpdate from "update-check";
import { createApp } from "./create-app";
import { getDataSources } from "./helpers/datasources";
import { getPkgManager } from "./helpers/get-pkg-manager";
import { isFolderEmpty } from "./helpers/is-folder-empty";
import { initializeGlobalAgent } from "./helpers/proxy";
import { runApp } from "./helpers/run-app";
import { getTools } from "./helpers/tools";
import { validateNpmName } from "./helpers/validate-pkg";
import packageJson from "./package.json";
import { QuestionArgs, askQuestions, onPromptState } from "./questions";
import { askQuestions } from "./questions/index";
import { QuestionArgs } from "./questions/types";
import { onPromptState } from "./questions/utils";
// Run the initialization function
initializeGlobalAgent();
@@ -29,12 +26,14 @@ const handleSigTerm = () => process.exit(0);
process.on("SIGINT", handleSigTerm);
process.on("SIGTERM", handleSigTerm);
const program = new Commander.Command(packageJson.name)
const program = new Command(packageJson.name)
.version(packageJson.version)
.arguments("<project-directory>")
.usage(`${green("<project-directory>")} [options]`)
.arguments("[project-directory]")
.usage(`${green("[project-directory]")} [options]`)
.action((name) => {
projectPath = name;
if (name) {
projectPath = name;
}
})
.option(
"--use-npm",
@@ -55,20 +54,6 @@ const program = new Commander.Command(packageJson.name)
`
Explicitly tell the CLI to bootstrap the application using Yarn
`,
)
.option(
"--reset-preferences",
`
Explicitly tell the CLI to reset any stored preferences
`,
)
.option(
"--template <template>",
`
Select a template to bootstrap the application with.
`,
)
.option(
@@ -76,41 +61,6 @@ const program = new Commander.Command(packageJson.name)
`
Select a framework to bootstrap the application with.
`,
)
.option(
"--files <path>",
`
Specify the path to a local file or folder for chatting.
`,
)
.option(
"--example-file",
`
Select to use an example PDF as data source.
`,
)
.option(
"--open-ai-key <key>",
`
Provide an OpenAI API key.
`,
)
.option(
"--ui <ui>",
`
Select a UI to bootstrap the application with.
`,
)
.option(
"--frontend",
`
Whether to generate a frontend for your backend.
`,
)
.option(
@@ -118,13 +68,6 @@ const program = new Commander.Command(packageJson.name)
`
Select UI port.
`,
)
.option(
"--external-port <external>",
`
Select external port.
`,
)
.option(
@@ -139,20 +82,6 @@ const program = new Commander.Command(packageJson.name)
`
Select which vector database you would like to use, such as 'none', 'pg' or 'mongo'. The default option is not to use a vector database and use the local filesystem instead ('none').
`,
)
.option(
"--tools <tools>",
`
Specify the tools you want to use by providing a comma-separated list. For example, 'wikipedia.WikipediaToolSpec,google.GoogleSearchToolSpec'. Use 'none' to not using any tools.
`,
)
.option(
"--use-llama-parse",
`
Enable LlamaParse.
`,
)
.option(
@@ -162,59 +91,39 @@ const program = new Commander.Command(packageJson.name)
Provide a LlamaCloud API key.
`,
)
.option(
"--observability <observability>",
`
Specify observability tools to use. Eg: none, opentelemetry
`,
)
.option(
"--ask-models",
`
Select LLM and embedding models.
Allow interactive selection of LLM and embedding models of different model providers.
`,
false,
)
.option(
"--use-case <useCase>",
`
Select which use case to use for the template (e.g: financial_report, blog).
`,
)
.allowUnknownOption()
.parse(process.argv);
if (process.argv.includes("--no-frontend")) {
program.frontend = false;
}
if (process.argv.includes("--tools")) {
if (program.tools === "none") {
program.tools = [];
} else {
program.tools = getTools(program.tools.split(","));
}
}
if (process.argv.includes("--no-llama-parse")) {
program.useLlamaParse = false;
}
program.askModels = process.argv.includes("--ask-models");
if (process.argv.includes("--no-files")) {
program.dataSources = [];
} else {
program.dataSources = getDataSources(program.files, program.exampleFile);
}
const packageManager = !!program.useNpm
const options = program.opts();
const packageManager = !!options.useNpm
? "npm"
: !!program.usePnpm
: !!options.usePnpm
? "pnpm"
: !!program.useYarn
: !!options.useYarn
? "yarn"
: getPkgManager();
// options above must use all the properties of QuestionArgs
const cliArgs = options as unknown as QuestionArgs;
async function run(): Promise<void> {
const conf = new Conf({ projectName: "create-llama" });
if (program.resetPreferences) {
conf.clear();
console.log(`Preferences reset successfully`);
return;
}
if (typeof projectPath === "string") {
projectPath = projectPath.trim();
}
@@ -277,35 +186,15 @@ async function run(): Promise<void> {
process.exit(1);
}
const preferences = (conf.get("preferences") || {}) as QuestionArgs;
await askQuestions(
program as unknown as QuestionArgs,
preferences,
program.openAiKey,
);
const answers = await askQuestions(cliArgs);
await createApp({
template: program.template,
framework: program.framework,
ui: program.ui,
...answers,
appPath: resolvedProjectPath,
packageManager,
frontend: program.frontend,
modelConfig: program.modelConfig,
llamaCloudKey: program.llamaCloudKey,
communityProjectConfig: program.communityProjectConfig,
llamapack: program.llamapack,
vectorDb: program.vectorDb,
externalPort: program.externalPort,
postInstallAction: program.postInstallAction,
dataSources: program.dataSources,
tools: program.tools,
useLlamaParse: program.useLlamaParse,
observability: program.observability,
});
conf.set("preferences", preferences);
if (program.postInstallAction === "VSCode") {
if (answers.postInstallAction === "VSCode") {
console.log(`Starting VSCode in ${root}...`);
try {
execSync(`code . --new-window --goto README.md`, {
@@ -329,15 +218,9 @@ Please check ${cyan(
)} for more information.`,
);
}
} else if (program.postInstallAction === "runApp") {
} else if (answers.postInstallAction === "runApp") {
console.log(`Running app in ${root}...`);
await runApp(
root,
program.frontend,
program.framework,
program.port,
program.externalPort,
);
await runApp(root, answers.template, answers.framework, options.port);
}
}
+75
View File
@@ -0,0 +1,75 @@
{
"name": "create-llama",
"version": "0.6.1",
"description": "Create LlamaIndex-powered apps with one command",
"keywords": [
"rag",
"llamaindex",
"next.js"
],
"repository": {
"type": "git",
"url": "https://github.com/run-llama/create-llama",
"directory": "packages/create-llama"
},
"license": "MIT",
"bin": {
"create-llama": "./dist/index.js"
},
"files": [
"dist",
"README.md",
"LICENSE.md"
],
"scripts": {
"copy": "cp -r ../../README.md ../../LICENSE.md .",
"build": "bash ./scripts/build.sh",
"build:ncc": "pnpm run clean && ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
"postbuild": "pnpm run copy",
"clean": "rimraf --glob ./dist ./templates/**/__pycache__ ./templates/**/node_modules ./templates/**/poetry.lock",
"dev": "ncc build ./index.ts -w -o dist/",
"e2e": "playwright test",
"e2e:python": "playwright test e2e/shared e2e/python",
"e2e:ts": "playwright test e2e/shared e2e/typescript",
"pack-install": "bash ./scripts/pack.sh"
},
"dependencies": {
"@types/async-retry": "1.4.2",
"@types/ci-info": "2.0.0",
"@types/cross-spawn": "6.0.0",
"@types/fs-extra": "11.0.4",
"@types/node": "^20.11.7",
"@types/prompts": "2.4.2",
"@types/tar": "6.1.5",
"@types/validate-npm-package-name": "3.0.0",
"async-retry": "1.3.1",
"async-sema": "3.0.1",
"commander": "12.1.0",
"cross-spawn": "7.0.3",
"fast-glob": "3.3.1",
"fs-extra": "11.2.0",
"global-agent": "^3.0.0",
"got": "10.7.0",
"ollama": "^0.5.0",
"ora": "^8.0.1",
"picocolors": "1.0.0",
"prompts": "2.4.2",
"smol-toml": "^1.1.4",
"tar": "6.1.15",
"terminal-link": "^3.0.0",
"update-check": "1.5.4",
"validate-npm-package-name": "3.0.0",
"yaml": "2.4.1"
},
"devDependencies": {
"@playwright/test": "^1.41.1",
"@vercel/ncc": "0.38.1",
"rimraf": "^5.0.5",
"typescript": "^5.3.3",
"wait-port": "^1.1.0"
},
"packageManager": "pnpm@9.0.5",
"engines": {
"node": ">=16.14.0"
}
}
@@ -1,4 +1,3 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
+149
View File
@@ -0,0 +1,149 @@
import prompts from "prompts";
import { askModelConfig } from "../helpers/providers";
import {
TemplateFramework,
TemplateUseCase,
TemplateVectorDB,
} from "../helpers/types";
import { QuestionArgs, QuestionResults } from "./types";
import { useCaseConfiguration } from "./usecases";
import { askPostInstallAction, questionHandlers } from "./utils";
export const askQuestions = async (
args: QuestionArgs,
): Promise<QuestionResults> => {
const {
useCase: useCaseFromArgs,
framework: frameworkFromArgs,
llamaCloudKey: llamaCloudKeyFromArgs,
vectorDb: vectorDbFromArgs,
postInstallAction: postInstallActionFromArgs,
askModels: askModelsFromArgs,
} = args;
const { useCase, framework } = await prompts(
[
{
type: useCaseFromArgs ? null : "select",
name: "useCase",
message: "What use case do you want to build?",
choices: [
{
title: "Agentic RAG",
value: "agentic_rag",
description:
"Chatbot that answers questions based on provided documents.",
},
{
title: "Financial Report",
value: "financial_report",
description:
"Agent that analyzes data and generates visualizations by using a code interpreter.",
},
{
title: "Deep Research",
value: "deep_research",
description:
"Researches and analyzes provided documents from multiple perspectives, generating a comprehensive report with citations to support key findings and insights.",
},
{
title: "Code Generator",
value: "code_generator",
description: "Build a Vercel v0 styled code generator.",
},
{
title: "Document Generator",
value: "document_generator",
description: "Build a OpenAI canvas-styled document generator.",
},
{
title: "Human in the Loop",
value: "hitl",
description:
"Build a CLI command workflow that is reviewed by a human before execution",
},
],
initial: 0,
},
{
type: frameworkFromArgs ? null : "select",
name: "framework",
message: "What language do you want to use?",
choices: [
{ title: "Python (FastAPI)", value: "fastapi" },
{ title: "Typescript (NextJS)", value: "nextjs" },
],
initial: 0,
},
],
questionHandlers,
);
const finalUseCase = (useCaseFromArgs ?? useCase) as TemplateUseCase;
const finalFramework = (frameworkFromArgs ?? framework) as TemplateFramework;
if (!finalUseCase) {
throw new Error("Use case is required");
}
if (!finalFramework) {
throw new Error("Framework is required");
}
// lookup configuration for the use case
const useCaseConfig = useCaseConfiguration[finalUseCase];
// Ask for model provider
let modelConfig = useCaseConfig.modelConfig;
if (askModelsFromArgs) {
modelConfig = await askModelConfig({
framework: finalFramework,
});
}
// Ask for LlamaCloud
let llamaCloudKey = llamaCloudKeyFromArgs ?? process.env.LLAMA_CLOUD_API_KEY;
let vectorDb: TemplateVectorDB = vectorDbFromArgs ?? "none";
if (!vectorDbFromArgs && useCaseConfig.dataSources) {
const { useLlamaCloud } = await prompts(
{
type: "toggle",
name: "useLlamaCloud",
message: "Do you want to use LlamaCloud?",
active: "Yes",
inactive: "No",
initial: false,
},
questionHandlers,
);
if (useLlamaCloud && !llamaCloudKey) {
const { llamaCloudKey: llamaCloudKeyFromPrompt } = await prompts(
{
type: "text",
name: "llamaCloudKey",
message:
"Please provide your LlamaCloud API key (leave blank to skip):",
},
questionHandlers,
);
llamaCloudKey = llamaCloudKeyFromPrompt;
}
vectorDb = useLlamaCloud ? "llamacloud" : "none";
}
const result = {
...useCaseConfig,
framework: finalFramework,
useCase: finalUseCase,
modelConfig,
llamaCloudKey,
useLlamaParse: vectorDb === "llamacloud",
vectorDb,
};
const postInstallAction =
postInstallActionFromArgs ?? (await askPostInstallAction(result));
return {
...result,
postInstallAction,
};
};
+36
View File
@@ -0,0 +1,36 @@
import fs from "fs";
import path from "path";
import { TemplateFramework } from "../helpers";
import { templatesDir } from "../helpers/dir";
export const getVectorDbChoices = (framework: TemplateFramework) => {
const choices = [
{
title: "No, just store the data in the file system",
value: "none",
},
{ title: "MongoDB", value: "mongo" },
{ title: "PostgreSQL", value: "pg" },
{ title: "Pinecone", value: "pinecone" },
{ title: "Milvus", value: "milvus" },
{ title: "Astra", value: "astra" },
{ title: "Qdrant", value: "qdrant" },
{ title: "ChromaDB", value: "chroma" },
{ title: "Weaviate", value: "weaviate" },
{ title: "LlamaCloud (use Managed Index)", value: "llamacloud" },
];
const vectordbLang = framework === "fastapi" ? "python" : "typescript";
const compPath = path.join(templatesDir, "components");
const vectordbPath = path.join(compPath, "vectordbs", vectordbLang);
const availableChoices = fs
.readdirSync(vectordbPath)
.filter((file) => fs.statSync(path.join(vectordbPath, file)).isDirectory());
const displayedChoices = choices.filter((choice) =>
availableChoices.includes(choice.value),
);
return displayedChoices;
};
+22
View File
@@ -0,0 +1,22 @@
import { InstallAppArgs } from "../create-app";
import {
TemplateFramework,
TemplatePostInstallAction,
TemplateUseCase,
TemplateVectorDB,
} from "../helpers";
export type QuestionResults = Omit<
InstallAppArgs,
"appPath" | "packageManager"
>;
export type QuestionArgs = {
useCase?: TemplateUseCase;
framework?: TemplateFramework;
askModels?: boolean;
llamaCloudKey?: string;
port?: number;
postInstallAction?: TemplatePostInstallAction;
vectorDb?: TemplateVectorDB;
};
@@ -0,0 +1,42 @@
import { EXAMPLE_10K_SEC_FILES, EXAMPLE_FILE } from "../helpers/datasources";
import { getGpt41ModelConfig } from "../helpers/models";
import { ModelConfig, TemplateUseCase } from "../helpers/types";
import { QuestionResults } from "./types";
export const useCaseConfiguration: Record<
TemplateUseCase,
Pick<QuestionResults, "template" | "dataSources"> & {
modelConfig: ModelConfig;
}
> = {
agentic_rag: {
template: "llamaindexserver",
dataSources: [EXAMPLE_FILE],
modelConfig: getGpt41ModelConfig(),
},
financial_report: {
template: "llamaindexserver",
dataSources: EXAMPLE_10K_SEC_FILES,
modelConfig: getGpt41ModelConfig(),
},
deep_research: {
template: "llamaindexserver",
dataSources: EXAMPLE_10K_SEC_FILES,
modelConfig: getGpt41ModelConfig(),
},
code_generator: {
template: "llamaindexserver",
dataSources: [],
modelConfig: getGpt41ModelConfig(),
},
document_generator: {
template: "llamaindexserver",
dataSources: [],
modelConfig: getGpt41ModelConfig(),
},
hitl: {
template: "llamaindexserver",
dataSources: [],
modelConfig: getGpt41ModelConfig(),
},
};
+172
View File
@@ -0,0 +1,172 @@
import { execSync } from "child_process";
import fs from "fs";
import path from "path";
import { red } from "picocolors";
import prompts from "prompts";
import { TemplateDataSourceType, TemplatePostInstallAction } from "../helpers";
import { QuestionResults } from "./types";
export const supportedContextFileTypes = [
".pdf",
".doc",
".docx",
".xls",
".xlsx",
".csv",
];
const MACOS_FILE_SELECTION_SCRIPT = `
osascript -l JavaScript -e '
a = Application.currentApplication();
a.includeStandardAdditions = true;
a.chooseFile({ withPrompt: "Please select files to process:", multipleSelectionsAllowed: true }).map(file => file.toString())
'`;
const MACOS_FOLDER_SELECTION_SCRIPT = `
osascript -l JavaScript -e '
a = Application.currentApplication();
a.includeStandardAdditions = true;
a.chooseFolder({ withPrompt: "Please select folders to process:", multipleSelectionsAllowed: true }).map(folder => folder.toString())
'`;
const WINDOWS_FILE_SELECTION_SCRIPT = `
Add-Type -AssemblyName System.Windows.Forms
$openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$openFileDialog.InitialDirectory = [Environment]::GetFolderPath('Desktop')
$openFileDialog.Multiselect = $true
$result = $openFileDialog.ShowDialog()
if ($result -eq 'OK') {
$openFileDialog.FileNames
}
`;
const WINDOWS_FOLDER_SELECTION_SCRIPT = `
Add-Type -AssemblyName System.windows.forms
$folderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog
$dialogResult = $folderBrowser.ShowDialog()
if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK)
{
$folderBrowser.SelectedPath
}
`;
export const selectLocalContextData = async (type: TemplateDataSourceType) => {
try {
let selectedPath: string = "";
let execScript: string;
let execOpts: any = {};
switch (process.platform) {
case "win32": // Windows
execScript =
type === "file"
? WINDOWS_FILE_SELECTION_SCRIPT
: WINDOWS_FOLDER_SELECTION_SCRIPT;
execOpts = { shell: "powershell.exe" };
break;
case "darwin": // MacOS
execScript =
type === "file"
? MACOS_FILE_SELECTION_SCRIPT
: MACOS_FOLDER_SELECTION_SCRIPT;
break;
default: // Unsupported OS
console.log(red("Unsupported OS error!"));
process.exit(1);
}
selectedPath = execSync(execScript, execOpts).toString().trim();
const paths =
process.platform === "win32"
? selectedPath.split("\r\n")
: selectedPath.split(", ");
for (const p of paths) {
if (
fs.statSync(p).isFile() &&
!supportedContextFileTypes.includes(path.extname(p))
) {
console.log(
red(
`Please select a supported file type: ${supportedContextFileTypes}`,
),
);
process.exit(1);
}
}
return paths;
} catch (error) {
console.log(
red(
"Got an error when trying to select local context data! Please try again or select another data source option.",
),
);
process.exit(1);
}
};
export const onPromptState = (state: any) => {
if (state.aborted) {
// If we don't re-enable the terminal cursor before exiting
// the program, the cursor will remain hidden
process.stdout.write("\x1B[?25h");
process.stdout.write("\n");
process.exit(1);
}
};
export const toChoice = (value: string) => {
return { title: value, value };
};
export const questionHandlers = {
onCancel: () => {
console.error("Exiting.");
process.exit(1);
},
};
// Ask for next action after installation
export async function askPostInstallAction(
args: Omit<QuestionResults, "postInstallAction">,
): Promise<TemplatePostInstallAction> {
const actionChoices = [
{
title: "Just generate code (~1 sec)",
value: "none",
},
{
title: "Start in VSCode (~1 sec)",
value: "VSCode",
},
{
title: "Generate code and install dependencies (~2 min)",
value: "dependencies",
},
];
const modelConfigured = args.modelConfig.isConfigured();
// If using LlamaParse, require LlamaCloud API key
const llamaCloudKeyConfigured = args.useLlamaParse
? args.llamaCloudKey || process.env["LLAMA_CLOUD_API_KEY"]
: true;
const hasVectorDb = args.vectorDb && args.vectorDb !== "none";
// Can run the app if all tools do not require configuration
if (!hasVectorDb && modelConfigured && llamaCloudKeyConfigured) {
actionChoices.push({
title: "Generate code, install dependencies, and run the app (~2 min)",
value: "runApp",
});
}
const { action } = await prompts(
{
type: "select",
name: "action",
message: "How would you like to proceed?",
choices: actionChoices,
initial: 1,
},
questionHandlers,
);
return action;
}
@@ -0,0 +1,68 @@
## Overview
This example is using three agents to generate a blog post:
- a researcher that retrieves content via a RAG pipeline,
- a writer that specializes in writing blog posts and
- a reviewer that is reviewing the blog post.
There are three different methods how the agents can interact to reach their goal:
1. [Choreography](./app/agents/choreography.py) - the agents decide themselves to delegate a task to another agent
1. [Orchestrator](./app/agents/orchestrator.py) - a central orchestrator decides which agent should execute a task
1. [Explicit Workflow](./app/agents/workflow.py) - a pre-defined workflow specific for the task is used to execute the tasks
## Getting Started
First, setup the environment with poetry:
> **_Note:_** This step is not needed if you are using the dev-container.
```shell
uv sync
```
Then check the parameters that have been pre-configured in the `.env` file in this directory. (E.g. you might need to configure an `OPENAI_API_KEY` if you're using OpenAI as model provider).
Second, generate the embeddings of the documents in the `./data` directory:
```shell
uv run generate
```
Third, run the development server:
```shell
uv run dev
```
Per default, the example is using the explicit workflow. You can change the example by setting the `EXAMPLE_TYPE` environment variable to `choreography` or `orchestrator`.
The example provides one streaming API endpoint `/api/chat`.
You can test the endpoint with the following curl request:
```
curl --location 'localhost:8000/api/chat' \
--header 'Content-Type: application/json' \
--data '{ "messages": [{ "role": "user", "content": "Write a blog post about physical standards for letters" }] }'
```
You can start editing the API by modifying `app/api/routers/chat.py` or `app/examples/workflow.py`. The API auto-updates as you save the files.
Open [http://localhost:8000](http://localhost:8000) with your browser to start the app.
To start the app optimized for **production**, run:
```
uv run prod
```
## Deployments
For production deployments, check the [DEPLOY.md](DEPLOY.md) file.
## Learn More
To learn more about LlamaIndex, take a look at the following resources:
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex.
- [Workflows Introduction](https://docs.llamaindex.ai/en/stable/understanding/workflows/) - learn about LlamaIndex workflows.
You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome!
@@ -0,0 +1,34 @@
from textwrap import dedent
from typing import List, Optional
from app.agents.publisher import create_publisher
from app.agents.researcher import create_researcher
from app.workflows.multi import AgentCallingAgent
from app.workflows.single import FunctionCallingAgent
from llama_index.core.chat_engine.types import ChatMessage
def create_choreography(chat_history: Optional[List[ChatMessage]] = None, **kwargs):
researcher = create_researcher(chat_history, **kwargs)
publisher = create_publisher(chat_history)
reviewer = FunctionCallingAgent(
name="reviewer",
description="expert in reviewing blog posts, needs a written post to review",
system_prompt="You are an expert in reviewing blog posts. You are given a task to review a blog post. Review the post for logical inconsistencies, ask critical questions, and provide suggestions for improvement. Furthermore, proofread the post for grammar and spelling errors. If the post is good, you can say 'The post is good.'",
chat_history=chat_history,
)
return AgentCallingAgent(
name="writer",
agents=[researcher, reviewer, publisher],
description="expert in writing blog posts, needs researched information and images to write a blog post",
system_prompt=dedent(
"""
You are an expert in writing blog posts. You are given a task to write a blog post. Before starting to write the post, consult the researcher agent to get the information you need. Don't make up any information yourself.
After creating a draft for the post, send it to the reviewer agent to receive feedback and make sure to incorporate the feedback from the reviewer.
You can consult the reviewer and researcher a maximum of two times. Your output should contain only the blog post.
Finally, always request the publisher to create a document (PDF, HTML) and publish the blog post.
"""
),
# TODO: add chat_history support to AgentCallingAgent
# chat_history=chat_history,
)
@@ -0,0 +1,44 @@
from textwrap import dedent
from typing import List, Optional
from app.agents.publisher import create_publisher
from app.agents.researcher import create_researcher
from app.workflows.multi import AgentOrchestrator
from app.workflows.single import FunctionCallingAgent
from llama_index.core.chat_engine.types import ChatMessage
def create_orchestrator(chat_history: Optional[List[ChatMessage]] = None, **kwargs):
researcher = create_researcher(chat_history, **kwargs)
writer = FunctionCallingAgent(
name="writer",
description="expert in writing blog posts, need information and images to write a post",
system_prompt=dedent(
"""
You are an expert in writing blog posts.
You are given a task to write a blog post. Do not make up any information yourself.
If you don't have the necessary information to write a blog post, reply "I need information about the topic to write the blog post".
If you need to use images, reply "I need images about the topic to write the blog post". Do not use any dummy images made up by you.
If you have all the information needed, write the blog post.
"""
),
chat_history=chat_history,
)
reviewer = FunctionCallingAgent(
name="reviewer",
description="expert in reviewing blog posts, needs a written blog post to review",
system_prompt=dedent(
"""
You are an expert in reviewing blog posts. You are given a task to review a blog post. Review the post and fix any issues found yourself. You must output a final blog post.
A post must include at least one valid image. If not, reply "I need images about the topic to write the blog post". An image URL starting with "example" or "your website" is not valid.
Especially check for logical inconsistencies and proofread the post for grammar and spelling errors.
"""
),
chat_history=chat_history,
)
publisher = create_publisher(chat_history)
return AgentOrchestrator(
agents=[writer, reviewer, researcher, publisher],
refine_plan=False,
chat_history=chat_history,
)
@@ -0,0 +1,35 @@
from textwrap import dedent
from typing import List, Tuple
from app.engine.tools import ToolFactory
from app.workflows.single import FunctionCallingAgent
from llama_index.core.chat_engine.types import ChatMessage
from llama_index.core.tools import FunctionTool
def get_publisher_tools() -> Tuple[List[FunctionTool], str, str]:
tools = []
# Get configured tools from the tools.yaml file
configured_tools = ToolFactory.from_env(map_result=True)
if "generate_document" in configured_tools.keys():
tools.append(configured_tools["generate_document"])
prompt_instructions = dedent("""
Normally, reply the blog post content to the user directly.
But if user requested to generate a file, use the generate_document tool to generate the file and reply the link to the file.
""")
description = "Expert in publishing the blog post, able to publish the blog post in PDF or HTML format."
else:
prompt_instructions = "You don't have a tool to generate document. Please reply the content directly."
description = "Expert in publishing the blog post"
return tools, prompt_instructions, description
def create_publisher(chat_history: List[ChatMessage]):
tools, prompt_instructions, description = get_publisher_tools()
return FunctionCallingAgent(
name="publisher",
tools=tools,
description=description,
system_prompt=prompt_instructions,
chat_history=chat_history,
)
@@ -0,0 +1,71 @@
from textwrap import dedent
from typing import List
from app.engine.index import IndexConfig, get_index
from app.engine.tools import ToolFactory
from app.workflows.single import FunctionCallingAgent
from llama_index.core.chat_engine.types import ChatMessage
from app.engine.tools.query_engine import get_query_engine_tool
def _get_research_tools(**kwargs):
"""
Researcher take responsibility for retrieving information.
Try init wikipedia or duckduckgo tool if available.
"""
tools = []
# Create query engine tool
index_config = IndexConfig(**kwargs)
index = get_index(index_config)
if index is not None:
query_engine_tool = get_query_engine_tool(index=index)
if query_engine_tool is not None:
tools.append(query_engine_tool)
# Create duckduckgo tool
researcher_tool_names = [
"duckduckgo_search",
"duckduckgo_image_search",
"wikipedia.WikipediaToolSpec",
]
configured_tools = ToolFactory.from_env(map_result=True)
for tool_name, tool in configured_tools.items():
if tool_name in researcher_tool_names:
tools.append(tool)
return tools
def create_researcher(chat_history: List[ChatMessage], **kwargs):
"""
Researcher is an agent that take responsibility for using tools to complete a given task.
"""
tools = _get_research_tools(**kwargs)
return FunctionCallingAgent(
name="researcher",
tools=tools,
description="expert in retrieving any unknown content or searching for images from the internet",
system_prompt=dedent(
"""
You are a researcher agent. You are given a research task.
If the conversation already includes the information and there is no new request for additional information from the user, you should return the appropriate content to the writer.
Otherwise, you must use tools to retrieve information or images needed for the task.
It's normal for the task to include some ambiguity. You must always think carefully about the context of the user's request to understand what are the main content needs to be retrieved.
Example:
Request: "Create a blog post about the history of the internet, write in English and publish in PDF format."
->Though: The main content is "history of the internet", while "write in English and publish in PDF format" is a requirement for other agents.
Your task: Look for information in English about the history of the Internet.
This is not your task: Create a blog post or look for how to create a PDF.
Next request: "Publish the blog post in HTML format."
->Though: User just asking for a format change, the previous content is still valid.
Your task: Return the previous content of the post to the writer. No need to do any research.
This is not your task: Look for how to create an HTML file.
If you use the tools but don't find any related information, please return "I didn't find any new information for {the topic}." along with the content you found. Don't try to make up information yourself.
If the request doesn't need any new information because it was in the conversation history, please return "The task doesn't need any new information. Please reuse the existing content in the conversation history."
"""
),
chat_history=chat_history,
)
@@ -0,0 +1,267 @@
from textwrap import dedent
from typing import AsyncGenerator, List, Optional
from app.agents.publisher import create_publisher
from app.agents.researcher import create_researcher
from app.workflows.single import AgentRunEvent, AgentRunResult, FunctionCallingAgent
from llama_index.core.chat_engine.types import ChatMessage
from llama_index.core.prompts import PromptTemplate
from llama_index.core.settings import Settings
from llama_index.core.workflow import (
Context,
Event,
StartEvent,
StopEvent,
Workflow,
step,
)
def create_workflow(chat_history: Optional[List[ChatMessage]] = None, **kwargs):
researcher = create_researcher(
chat_history=chat_history,
**kwargs,
)
publisher = create_publisher(
chat_history=chat_history,
)
writer = FunctionCallingAgent(
name="writer",
description="expert in writing blog posts, need information and images to write a post.",
system_prompt=dedent(
"""
You are an expert in writing blog posts.
You are given the task of writing a blog post based on research content provided by the researcher agent. Do not invent any information yourself.
It's important to read the entire conversation history to write the blog post accurately.
If you receive a review from the reviewer, update the post according to the feedback and return the new post content.
If the content is not valid (e.g., broken link, broken image, etc.), do not use it.
It's normal for the task to include some ambiguity, so you must define the user's initial request to write the post correctly.
If you update the post based on the reviewer's feedback, first explain what changes you made to the post, then provide the new post content. Do not include the reviewer's comments.
Example:
Task: "Here is the information I found about the history of the internet:
Create a blog post about the history of the internet, write in English, and publish in PDF format."
-> Your task: Use the research content {...} to write a blog post in English.
-> This is not your task: Create a PDF
Please note that a localhost link is acceptable, but dummy links like "example.com" or "your-website.com" are not valid.
"""
),
chat_history=chat_history,
)
reviewer = FunctionCallingAgent(
name="reviewer",
description="expert in reviewing blog posts, needs a written blog post to review.",
system_prompt=dedent(
"""
You are an expert in reviewing blog posts.
You are given a task to review a blog post. As a reviewer, it's important that your review aligns with the user's request. Please focus on the user's request when reviewing the post.
Review the post for logical inconsistencies, ask critical questions, and provide suggestions for improvement.
Furthermore, proofread the post for grammar and spelling errors.
Only if the post is good enough for publishing should you return 'The post is good.' In all other cases, return your review.
It's normal for the task to include some ambiguity, so you must define the user's initial request to review the post correctly.
Please note that a localhost link is acceptable, but dummy links like "example.com" or "your-website.com" are not valid.
Example:
Task: "Create a blog post about the history of the internet, write in English and publish in PDF format."
-> Your task: Review whether the main content of the post is about the history of the internet and if it is written in English.
-> This is not your task: Create blog post, create PDF, write in English.
"""
),
chat_history=chat_history,
)
workflow = BlogPostWorkflow(
timeout=360, chat_history=chat_history
) # Pass chat_history here
workflow.add_workflows(
researcher=researcher,
writer=writer,
reviewer=reviewer,
publisher=publisher,
)
return workflow
class ResearchEvent(Event):
input: str
class WriteEvent(Event):
input: str
is_good: bool = False
class ReviewEvent(Event):
input: str
class PublishEvent(Event):
input: str
class BlogPostWorkflow(Workflow):
def __init__(
self, timeout: int = 360, chat_history: Optional[List[ChatMessage]] = None
):
super().__init__(timeout=timeout)
self.chat_history = chat_history or []
@step()
async def start(self, ctx: Context, ev: StartEvent) -> ResearchEvent | PublishEvent:
# set streaming
ctx.data["streaming"] = getattr(ev, "streaming", False)
# start the workflow with researching about a topic
ctx.data["task"] = ev.input
ctx.data["user_input"] = ev.input
# Decision-making process
decision = await self._decide_workflow(ev.input, self.chat_history)
if decision != "publish":
return ResearchEvent(input=f"Research for this task: {ev.input}")
else:
chat_history_str = "\n".join(
[f"{msg.role}: {msg.content}" for msg in self.chat_history]
)
return PublishEvent(
input=f"Please publish content based on the chat history\n{chat_history_str}\n\n and task: {ev.input}"
)
async def _decide_workflow(
self, input: str, chat_history: List[ChatMessage]
) -> str:
prompt_template = PromptTemplate(
dedent(
"""
You are an expert in decision-making, helping people write and publish blog posts.
If the user is asking for a file or to publish content, respond with 'publish'.
If the user requests to write or update a blog post, respond with 'not_publish'.
Here is the chat history:
{chat_history}
The current user request is:
{input}
Given the chat history and the new user request, decide whether to publish based on existing information.
Decision (respond with either 'not_publish' or 'publish'):
"""
)
)
chat_history_str = "\n".join(
[f"{msg.role}: {msg.content}" for msg in chat_history]
)
prompt = prompt_template.format(chat_history=chat_history_str, input=input)
output = await Settings.llm.acomplete(prompt)
decision = output.text.strip().lower()
return "publish" if decision == "publish" else "research"
@step()
async def research(
self, ctx: Context, ev: ResearchEvent, researcher: FunctionCallingAgent
) -> WriteEvent:
result: AgentRunResult = await self.run_agent(ctx, researcher, ev.input)
content = result.response.message.content
return WriteEvent(
input=f"Write a blog post given this task: {ctx.data['task']} using this research content: {content}"
)
@step()
async def write(
self, ctx: Context, ev: WriteEvent, writer: FunctionCallingAgent
) -> ReviewEvent | StopEvent:
MAX_ATTEMPTS = 2
ctx.data["attempts"] = ctx.data.get("attempts", 0) + 1
too_many_attempts = ctx.data["attempts"] > MAX_ATTEMPTS
if too_many_attempts:
ctx.write_event_to_stream(
AgentRunEvent(
name=writer.name,
msg=f"Too many attempts ({MAX_ATTEMPTS}) to write the blog post. Proceeding with the current version.",
)
)
if ev.is_good or too_many_attempts:
# too many attempts or the blog post is good - stream final response if requested
result = await self.run_agent(
ctx,
writer,
f"Based on the reviewer's feedback, refine the post and return only the final version of the post. Here's the current version: {ev.input}",
streaming=ctx.data["streaming"],
)
return StopEvent(result=result)
result: AgentRunResult = await self.run_agent(ctx, writer, ev.input)
ctx.data["result"] = result
return ReviewEvent(input=result.response.message.content)
@step()
async def review(
self, ctx: Context, ev: ReviewEvent, reviewer: FunctionCallingAgent
) -> WriteEvent:
result: AgentRunResult = await self.run_agent(ctx, reviewer, ev.input)
review = result.response.message.content
old_content = ctx.data["result"].response.message.content
post_is_good = "post is good" in review.lower()
ctx.write_event_to_stream(
AgentRunEvent(
name=reviewer.name,
msg=f"The post is {'not ' if not post_is_good else ''}good enough for publishing. Sending back to the writer{' for publication.' if post_is_good else '.'}",
)
)
if post_is_good:
return WriteEvent(
input=f"You're blog post is ready for publication. Please respond with just the blog post. Blog post: ```{old_content}```",
is_good=True,
)
else:
return WriteEvent(
input=dedent(
f"""
Improve the writing of a given blog post by using a given review.
Blog post:
```
{old_content}
```
Review:
```
{review}
```
"""
),
)
@step()
async def publish(
self,
ctx: Context,
ev: PublishEvent,
publisher: FunctionCallingAgent,
) -> StopEvent:
try:
result: AgentRunResult = await self.run_agent(
ctx, publisher, ev.input, streaming=ctx.data["streaming"]
)
return StopEvent(result=result)
except Exception as e:
ctx.write_event_to_stream(
AgentRunEvent(
name=publisher.name,
msg=f"Error publishing: {e}",
)
)
return StopEvent(result=None)
async def run_agent(
self,
ctx: Context,
agent: FunctionCallingAgent,
input: str,
streaming: bool = False,
) -> AgentRunResult | AsyncGenerator:
handler = agent.run(input=input, streaming=streaming)
# bubble all events while running the executor to the planner
async for event in handler.stream_events():
# Don't write the StopEvent from sub task to the stream
if type(event) is not StopEvent:
ctx.write_event_to_stream(event)
return await handler
@@ -0,0 +1,3 @@
from .blog import create_workflow
__all__ = ["create_workflow"]
@@ -0,0 +1,30 @@
import logging
import os
from typing import List, Optional
from app.agents.choreography import create_choreography
from app.agents.orchestrator import create_orchestrator
from app.agents.workflow import create_workflow as create_blog_workflow
from llama_index.core.chat_engine.types import ChatMessage
from llama_index.core.workflow import Workflow
logger = logging.getLogger("uvicorn")
def create_workflow(
chat_history: Optional[List[ChatMessage]] = None, **kwargs
) -> Workflow:
# Chat filters are not supported yet
kwargs.pop("filters", None)
agent_type = os.getenv("EXAMPLE_TYPE", "").lower()
match agent_type:
case "choreography":
agent = create_choreography(chat_history, **kwargs)
case "orchestrator":
agent = create_orchestrator(chat_history, **kwargs)
case _:
agent = create_blog_workflow(chat_history, **kwargs)
logger.info(f"Using agent pattern: {agent_type}")
return agent
@@ -0,0 +1,86 @@
from typing import Any, List
from app.workflows.planner import StructuredPlannerAgent
from app.workflows.single import (
AgentRunResult,
ContextAwareTool,
FunctionCallingAgent,
)
from llama_index.core.tools.types import ToolMetadata, ToolOutput
from llama_index.core.tools.utils import create_schema_from_function
from llama_index.core.workflow import Context, StopEvent, Workflow
class AgentCallTool(ContextAwareTool):
def __init__(self, agent: Workflow) -> None:
self.agent = agent
name = f"call_{agent.name}"
async def schema_call(input: str) -> str:
pass
# create the schema without the Context
fn_schema = create_schema_from_function(name, schema_call)
self._metadata = ToolMetadata(
name=name,
description=(
f"Use this tool to delegate a sub task to the {agent.name} agent."
+ (
f" The agent is an {agent.description}."
if agent.description
else ""
)
),
fn_schema=fn_schema,
)
# overload the acall function with the ctx argument as it's needed for bubbling the events
async def acall(self, ctx: Context, input: str) -> ToolOutput:
handler = self.agent.run(input=input)
# bubble all events while running the agent to the calling agent
async for ev in handler.stream_events():
if type(ev) is not StopEvent:
ctx.write_event_to_stream(ev)
ret: AgentRunResult = await handler
response = ret.response.message.content
return ToolOutput(
content=str(response),
tool_name=self.metadata.name,
raw_input={"args": input, "kwargs": {}},
raw_output=response,
)
class AgentCallingAgent(FunctionCallingAgent):
def __init__(
self,
*args: Any,
name: str,
agents: List[FunctionCallingAgent] | None = None,
**kwargs: Any,
) -> None:
agents = agents or []
tools = [AgentCallTool(agent=agent) for agent in agents]
super().__init__(*args, name=name, tools=tools, **kwargs)
# call add_workflows so agents will get detected by llama agents automatically
self.add_workflows(**{agent.name: agent for agent in agents})
class AgentOrchestrator(StructuredPlannerAgent):
def __init__(
self,
*args: Any,
name: str = "orchestrator",
agents: List[FunctionCallingAgent] | None = None,
**kwargs: Any,
) -> None:
agents = agents or []
tools = [AgentCallTool(agent=agent) for agent in agents]
super().__init__(
*args,
name=name,
tools=tools,
**kwargs,
)
# call add_workflows so agents will get detected by llama agents automatically
self.add_workflows(**{agent.name: agent for agent in agents})
@@ -0,0 +1,347 @@
import uuid
from enum import Enum
from typing import Any, AsyncGenerator, Dict, List, Optional, Tuple, Union
from app.workflows.single import AgentRunEvent, AgentRunResult, FunctionCallingAgent
from llama_index.core.agent.runner.planner import (
DEFAULT_INITIAL_PLAN_PROMPT,
DEFAULT_PLAN_REFINE_PROMPT,
Plan,
PlannerAgentState,
SubTask,
)
from llama_index.core.bridge.pydantic import ValidationError
from llama_index.core.chat_engine.types import ChatMessage
from llama_index.core.llms.function_calling import FunctionCallingLLM
from llama_index.core.prompts import PromptTemplate
from llama_index.core.settings import Settings
from llama_index.core.tools import BaseTool
from llama_index.core.workflow import (
Context,
Event,
StartEvent,
StopEvent,
Workflow,
step,
)
INITIAL_PLANNER_PROMPT = """\
Think step-by-step. Given a conversation, set of tools and a user request. Your responsibility is to create a plan to complete the task.
The plan must adapt with the user request and the conversation.
The tools available are:
{tools_str}
Conversation: {chat_history}
Overall Task: {task}
"""
class ExecutePlanEvent(Event):
pass
class SubTaskEvent(Event):
sub_task: SubTask
class SubTaskResultEvent(Event):
sub_task: SubTask
result: AgentRunResult | AsyncGenerator
class PlanEventType(Enum):
CREATED = "created"
REFINED = "refined"
class PlanEvent(AgentRunEvent):
event_type: PlanEventType
plan: Plan
@property
def msg(self) -> str:
sub_task_names = ", ".join(task.name for task in self.plan.sub_tasks)
return f"Plan {self.event_type.value}: Let's do: {sub_task_names}"
class StructuredPlannerAgent(Workflow):
def __init__(
self,
*args: Any,
name: str,
llm: FunctionCallingLLM | None = None,
tools: List[BaseTool] | None = None,
timeout: float = 360.0,
refine_plan: bool = False,
chat_history: Optional[List[ChatMessage]] = None,
**kwargs: Any,
) -> None:
super().__init__(*args, timeout=timeout, **kwargs)
self.name = name
self.refine_plan = refine_plan
self.chat_history = chat_history
self.tools = tools or []
self.planner = Planner(
llm=llm,
tools=self.tools,
initial_plan_prompt=INITIAL_PLANNER_PROMPT,
verbose=self._verbose,
)
# The executor is keeping the memory of all tool calls and decides to call the right tool for the task
self.executor = FunctionCallingAgent(
name="executor",
llm=llm,
tools=self.tools,
write_events=False,
# it's important to instruct to just return the tool call, otherwise the executor will interpret and change the result
system_prompt="You are an expert in completing given tasks by calling the right tool for the task. Just return the result of the tool call. Don't add any information yourself",
)
self.add_workflows(executor=self.executor)
@step()
async def create_plan(
self, ctx: Context, ev: StartEvent
) -> ExecutePlanEvent | StopEvent:
# set streaming
ctx.data["streaming"] = getattr(ev, "streaming", False)
ctx.data["task"] = ev.input
plan_id, plan = await self.planner.create_plan(
input=ev.input, chat_history=self.chat_history
)
ctx.data["act_plan_id"] = plan_id
# inform about the new plan
ctx.write_event_to_stream(
PlanEvent(name=self.name, event_type=PlanEventType.CREATED, plan=plan)
)
if self._verbose:
print("=== Executing plan ===\n")
return ExecutePlanEvent()
@step()
async def execute_plan(self, ctx: Context, ev: ExecutePlanEvent) -> SubTaskEvent:
upcoming_sub_tasks = self.planner.state.get_next_sub_tasks(
ctx.data["act_plan_id"]
)
if upcoming_sub_tasks:
# Execute only the first sub-task
# otherwise the executor will get over-lapping messages
# alternatively, we could use one executor for all sub tasks
next_sub_task = upcoming_sub_tasks[0]
return SubTaskEvent(sub_task=next_sub_task)
return None
@step()
async def execute_sub_task(
self, ctx: Context, ev: SubTaskEvent
) -> SubTaskResultEvent:
if self._verbose:
print(f"=== Executing sub task: {ev.sub_task.name} ===")
is_last_tasks = self.get_remaining_subtasks(ctx) == 1
# TODO: streaming only works without plan refining
streaming = is_last_tasks and ctx.data["streaming"] and not self.refine_plan
handler = self.executor.run(
input=ev.sub_task.input,
streaming=streaming,
)
# bubble all events while running the executor to the planner
async for event in handler.stream_events():
# Don't write the StopEvent from sub task to the stream
if type(event) is not StopEvent:
ctx.write_event_to_stream(event)
result: AgentRunResult = await handler
if self._verbose:
print("=== Done executing sub task ===\n")
self.planner.state.add_completed_sub_task(ctx.data["act_plan_id"], ev.sub_task)
return SubTaskResultEvent(sub_task=ev.sub_task, result=result)
@step()
async def gather_results(
self, ctx: Context, ev: SubTaskResultEvent
) -> ExecutePlanEvent | StopEvent:
result = ev
upcoming_sub_tasks = self.get_upcoming_sub_tasks(ctx)
# if no more tasks to do, stop workflow and send result of last step
if upcoming_sub_tasks == 0:
return StopEvent(result=result.result)
if self.refine_plan:
# store the result for refining the plan
ctx.data["results"] = ctx.data.get("results", {})
ctx.data["results"][result.sub_task.name] = result.result
new_plan = await self.planner.refine_plan(
ctx.data["task"], ctx.data["act_plan_id"], ctx.data["results"]
)
# inform about the new plan
if new_plan is not None:
ctx.write_event_to_stream(
PlanEvent(
name=self.name, event_type=PlanEventType.REFINED, plan=new_plan
)
)
# continue executing plan
return ExecutePlanEvent()
def get_upcoming_sub_tasks(self, ctx: Context):
upcoming_sub_tasks = self.planner.state.get_next_sub_tasks(
ctx.data["act_plan_id"]
)
return len(upcoming_sub_tasks)
def get_remaining_subtasks(self, ctx: Context):
remaining_subtasks = self.planner.state.get_remaining_subtasks(
ctx.data["act_plan_id"]
)
return len(remaining_subtasks)
# Concern dealing with creating and refining a plan, extracted from https://github.com/run-llama/llama_index/blob/main/llama-index-core/llama_index/core/agent/runner/planner.py#L138
class Planner:
def __init__(
self,
llm: FunctionCallingLLM | None = None,
tools: List[BaseTool] | None = None,
initial_plan_prompt: Union[str, PromptTemplate] = DEFAULT_INITIAL_PLAN_PROMPT,
plan_refine_prompt: Union[str, PromptTemplate] = DEFAULT_PLAN_REFINE_PROMPT,
verbose: bool = True,
) -> None:
if llm is None:
llm = Settings.llm
self.llm = llm
assert self.llm.metadata.is_function_calling_model
self.tools = tools or []
self.state = PlannerAgentState()
self.verbose = verbose
if isinstance(initial_plan_prompt, str):
initial_plan_prompt = PromptTemplate(initial_plan_prompt)
self.initial_plan_prompt = initial_plan_prompt
if isinstance(plan_refine_prompt, str):
plan_refine_prompt = PromptTemplate(plan_refine_prompt)
self.plan_refine_prompt = plan_refine_prompt
async def create_plan(
self, input: str, chat_history: Optional[List[ChatMessage]] = None
) -> Tuple[str, Plan]:
tools = self.tools
tools_str = ""
for tool in tools:
tools_str += tool.metadata.name + ": " + tool.metadata.description + "\n"
try:
plan = await self.llm.astructured_predict(
Plan,
self.initial_plan_prompt,
tools_str=tools_str,
task=input,
chat_history=chat_history,
)
except (ValueError, ValidationError):
if self.verbose:
print("No complex plan predicted. Defaulting to a single task plan.")
plan = Plan(
sub_tasks=[
SubTask(
name="default", input=input, expected_output="", dependencies=[]
)
]
)
if self.verbose:
print("=== Initial plan ===")
for sub_task in plan.sub_tasks:
print(
f"{sub_task.name}:\n{sub_task.input} -> {sub_task.expected_output}\ndeps: {sub_task.dependencies}\n\n"
)
plan_id = str(uuid.uuid4())
self.state.plan_dict[plan_id] = plan
return plan_id, plan
async def refine_plan(
self,
input: str,
plan_id: str,
completed_sub_tasks: Dict[str, str],
) -> Optional[Plan]:
"""Refine a plan."""
prompt_args = self.get_refine_plan_prompt_kwargs(
plan_id, input, completed_sub_tasks
)
try:
new_plan = await self.llm.astructured_predict(
Plan, self.plan_refine_prompt, **prompt_args
)
self._update_plan(plan_id, new_plan)
return new_plan
except (ValueError, ValidationError) as e:
# likely no new plan predicted
if self.verbose:
print(f"No new plan predicted: {e}")
return None
def _update_plan(self, plan_id: str, new_plan: Plan) -> None:
"""Update the plan."""
# update state with new plan
self.state.plan_dict[plan_id] = new_plan
if self.verbose:
print("=== Refined plan ===")
for sub_task in new_plan.sub_tasks:
print(
f"{sub_task.name}:\n{sub_task.input} -> {sub_task.expected_output}\ndeps: {sub_task.dependencies}\n\n"
)
def get_refine_plan_prompt_kwargs(
self,
plan_id: str,
task: str,
completed_sub_task: Dict[str, str],
) -> dict:
"""Get the refine plan prompt."""
# gather completed sub-tasks and response pairs
completed_outputs_str = ""
for sub_task_name, task_output in completed_sub_task.items():
task_str = f"{sub_task_name}:\n\t{task_output!s}\n"
completed_outputs_str += task_str
# get a string for the remaining sub-tasks
remaining_sub_tasks = self.state.get_remaining_subtasks(plan_id)
remaining_sub_tasks_str = "" if len(remaining_sub_tasks) != 0 else "None"
for sub_task in remaining_sub_tasks:
task_str = (
f"SubTask(name='{sub_task.name}', "
f"input='{sub_task.input}', "
f"expected_output='{sub_task.expected_output}', "
f"dependencies='{sub_task.dependencies}')\n"
)
remaining_sub_tasks_str += task_str
# get the tools string
tools = self.tools
tools_str = ""
for tool in tools:
tools_str += tool.metadata.name + ": " + tool.metadata.description + "\n"
# return the kwargs
return {
"tools_str": tools_str.strip(),
"task": task.strip(),
"completed_outputs": completed_outputs_str.strip(),
"remaining_sub_tasks": remaining_sub_tasks_str.strip(),
}
@@ -0,0 +1,254 @@
from abc import abstractmethod
from enum import Enum
from typing import Any, AsyncGenerator, List, Optional
from llama_index.core.llms import ChatMessage, ChatResponse
from llama_index.core.llms.function_calling import FunctionCallingLLM
from llama_index.core.memory import ChatMemoryBuffer
from llama_index.core.settings import Settings
from llama_index.core.tools import FunctionTool, ToolOutput, ToolSelection
from llama_index.core.tools.types import BaseTool
from llama_index.core.workflow import (
Context,
Event,
StartEvent,
StopEvent,
Workflow,
step,
)
from pydantic import BaseModel, Field
class InputEvent(Event):
input: list[ChatMessage]
class ToolCallEvent(Event):
tool_calls: list[ToolSelection]
class AgentRunEventType(Enum):
TEXT = "text"
PROGRESS = "progress"
class AgentRunEvent(Event):
name: str
msg: str
event_type: AgentRunEventType = Field(default=AgentRunEventType.TEXT)
data: Optional[dict] = None
def to_response(self) -> dict:
return {
"type": "agent",
"data": {
"agent": self.name,
"type": self.event_type.value,
"text": self.msg,
"data": self.data,
},
}
class AgentRunResult(BaseModel):
response: ChatResponse
sources: list[ToolOutput]
class ContextAwareTool(FunctionTool):
@abstractmethod
async def acall(self, ctx: Context, input: Any) -> ToolOutput:
pass
class FunctionCallingAgent(Workflow):
def __init__(
self,
*args: Any,
llm: FunctionCallingLLM | None = None,
chat_history: Optional[List[ChatMessage]] = None,
tools: List[BaseTool] | None = None,
system_prompt: str | None = None,
verbose: bool = False,
timeout: float = 360.0,
name: str,
write_events: bool = True,
description: str | None = None,
**kwargs: Any,
) -> None:
super().__init__(*args, verbose=verbose, timeout=timeout, **kwargs)
self.tools = tools or []
self.name = name
self.write_events = write_events
self.description = description
if llm is None:
llm = Settings.llm
self.llm = llm
assert self.llm.metadata.is_function_calling_model
self.system_prompt = system_prompt
self.memory = ChatMemoryBuffer.from_defaults(
llm=self.llm, chat_history=chat_history
)
self.sources = []
@step()
async def prepare_chat_history(self, ctx: Context, ev: StartEvent) -> InputEvent:
# clear sources
self.sources = []
# set system prompt
if self.system_prompt is not None:
system_msg = ChatMessage(role="system", content=self.system_prompt)
self.memory.put(system_msg)
# set streaming
ctx.data["streaming"] = getattr(ev, "streaming", False)
# get user input
user_input = ev.input
user_msg = ChatMessage(role="user", content=user_input)
self.memory.put(user_msg)
if self.write_events:
ctx.write_event_to_stream(
AgentRunEvent(name=self.name, msg=f"Start to work on: {user_input}")
)
# get chat history
chat_history = self.memory.get()
return InputEvent(input=chat_history)
@step()
async def handle_llm_input(
self, ctx: Context, ev: InputEvent
) -> ToolCallEvent | StopEvent:
if ctx.data["streaming"]:
return await self.handle_llm_input_stream(ctx, ev)
chat_history = ev.input
response = await self.llm.achat_with_tools(
self.tools, chat_history=chat_history
)
self.memory.put(response.message)
tool_calls = self.llm.get_tool_calls_from_response(
response, error_on_no_tool_call=False
)
if not tool_calls:
if self.write_events:
ctx.write_event_to_stream(
AgentRunEvent(name=self.name, msg="Finished task")
)
return StopEvent(
result=AgentRunResult(response=response, sources=[*self.sources])
)
else:
return ToolCallEvent(tool_calls=tool_calls)
async def handle_llm_input_stream(
self, ctx: Context, ev: InputEvent
) -> ToolCallEvent | StopEvent:
chat_history = ev.input
async def response_generator() -> AsyncGenerator:
response_stream = await self.llm.astream_chat_with_tools(
self.tools, chat_history=chat_history
)
full_response = None
yielded_indicator = False
async for chunk in response_stream:
if "tool_calls" not in chunk.message.additional_kwargs:
# Yield a boolean to indicate whether the response is a tool call
if not yielded_indicator:
yield False
yielded_indicator = True
# if not a tool call, yield the chunks!
yield chunk
elif not yielded_indicator:
# Yield the indicator for a tool call
yield True
yielded_indicator = True
full_response = chunk
# Write the full response to memory
self.memory.put(full_response.message)
# Yield the final response
yield full_response
# Start the generator
generator = response_generator()
# Check for immediate tool call
is_tool_call = await generator.__anext__()
if is_tool_call:
full_response = await generator.__anext__()
tool_calls = self.llm.get_tool_calls_from_response(full_response)
return ToolCallEvent(tool_calls=tool_calls)
# If we've reached here, it's not an immediate tool call, so we return the generator
if self.write_events:
ctx.write_event_to_stream(
AgentRunEvent(name=self.name, msg="Finished task")
)
return StopEvent(result=generator)
@step()
async def handle_tool_calls(self, ctx: Context, ev: ToolCallEvent) -> InputEvent:
tool_calls = ev.tool_calls
tools_by_name = {tool.metadata.get_name(): tool for tool in self.tools}
tool_msgs = []
# call tools -- safely!
for tool_call in tool_calls:
tool = tools_by_name.get(tool_call.tool_name)
additional_kwargs = {
"tool_call_id": tool_call.tool_id,
"name": tool.metadata.get_name(),
}
if not tool:
tool_msgs.append(
ChatMessage(
role="tool",
content=f"Tool {tool_call.tool_name} does not exist",
additional_kwargs=additional_kwargs,
)
)
continue
try:
if isinstance(tool, ContextAwareTool):
# inject context for calling an context aware tool
tool_output = await tool.acall(ctx=ctx, **tool_call.tool_kwargs)
else:
tool_output = await tool.acall(**tool_call.tool_kwargs)
self.sources.append(tool_output)
tool_msgs.append(
ChatMessage(
role="tool",
content=tool_output.content,
additional_kwargs=additional_kwargs,
)
)
except Exception as e:
tool_msgs.append(
ChatMessage(
role="tool",
content=f"Encountered error in tool call: {e}",
additional_kwargs=additional_kwargs,
)
)
for msg in tool_msgs:
self.memory.put(msg)
chat_history = self.memory.get()
return InputEvent(input=chat_history)
@@ -0,0 +1,47 @@
This is a [LlamaIndex](https://www.llamaindex.ai/) multi-agents project using [Workflows](https://docs.llamaindex.ai/en/stable/understanding/workflows/).
## Getting Started
First, setup the environment with poetry:
> **_Note:_** This step is not needed if you are using the dev-container.
```shell
uv sync
```
Then check the parameters that have been pre-configured in the `.env` file in this directory. (E.g. you might need to configure an `OPENAI_API_KEY` if you're using OpenAI as model provider).
Second, generate the embeddings of the documents in the `./data` directory:
```shell
uv run generate
```
Third, run the development server:
```shell
uv run dev
```
## Use Case: Deep Research over own documents
The workflow performs deep research by retrieving and analyzing documents from the [data](./data) directory from multiple perspectives. The project includes a sample PDF about AI investment in 2024 to help you get started. You can also add your own documents by placing them in the data directory and running the generate script again to index them.
After starting the server, go to [http://localhost:8000](http://localhost:8000) and send a message to the agent to write a blog post.
E.g: "AI investment in 2024"
To update the workflow, you can edit the [deep_research.py](./app/workflows/deep_research.py) file.
By default, the workflow retrieves 10 results from your documents. To customize the amount of information covered in the answer, you can adjust the `TOP_K` environment variable in the `.env` file. A higher value will retrieve more results from your documents, potentially providing more comprehensive answers.
## Deployments
For production deployments, check the [DEPLOY.md](DEPLOY.md) file.
## Learn More
To learn more about LlamaIndex, take a look at the following resources:
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex.
- [Workflows Introduction](https://docs.llamaindex.ai/en/stable/understanding/workflows/) - learn about LlamaIndex workflows.
You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome!
@@ -0,0 +1,3 @@
from .deep_research import create_workflow
__all__ = ["create_workflow"]
@@ -0,0 +1,183 @@
from typing import List, Literal, Optional
from llama_index.core.base.llms.types import (
CompletionResponse,
CompletionResponseAsyncGen,
)
from llama_index.core.memory.simple_composable_memory import SimpleComposableMemory
from llama_index.core.prompts import PromptTemplate
from llama_index.core.schema import MetadataMode, Node, NodeWithScore
from llama_index.core.settings import Settings
from pydantic import BaseModel, Field
class AnalysisDecision(BaseModel):
decision: Literal["research", "write", "cancel"] = Field(
description="Whether to continue research, write a report, or cancel the research after several retries"
)
research_questions: Optional[List[str]] = Field(
description="""
If the decision is to research, provide a list of questions to research that related to the user request.
Maximum 3 questions. Set to null or empty if writing a report or cancel the research.
""",
default_factory=list,
)
cancel_reason: Optional[str] = Field(
description="The reason for cancellation if the decision is to cancel research.",
default=None,
)
async def plan_research(
memory: SimpleComposableMemory,
context_nodes: List[Node],
user_request: str,
total_questions: int,
) -> AnalysisDecision:
analyze_prompt = """
You are a professor who is guiding a researcher to research a specific request/problem.
Your task is to decide on a research plan for the researcher.
The possible actions are:
+ Provide a list of questions for the researcher to investigate, with the purpose of clarifying the request.
+ Write a report if the researcher has already gathered enough research on the topic and can resolve the initial request.
+ Cancel the research if most of the answers from researchers indicate there is insufficient information to research the request. Do not attempt more than 3 research iterations or too many questions.
The workflow should be:
+ Always begin by providing some initial questions for the researcher to investigate.
+ Analyze the provided answers against the initial topic/request. If the answers are insufficient to resolve the initial request, provide additional questions for the researcher to investigate.
+ If the answers are sufficient to resolve the initial request, instruct the researcher to write a report.
Here are the context:
<Collected information>
{context_str}
</Collected information>
<Conversation context>
{conversation_context}
</Conversation context>
{enhanced_prompt}
Now, provide your decision in the required format for this user request:
<User request>
{user_request}
</User request>
"""
# Manually craft the prompt to avoid LLM hallucination
enhanced_prompt = ""
if total_questions == 0:
# Avoid writing a report without any research context
enhanced_prompt = """
The student has no questions to research. Let start by asking some questions.
"""
elif total_questions > 6:
# Avoid asking too many questions (when the data is not ready for writing a report)
enhanced_prompt = f"""
The student has researched {total_questions} questions. Should cancel the research if the context is not enough to write a report.
"""
conversation_context = "\n".join(
[f"{message.role}: {message.content}" for message in memory.get_all()]
)
context_str = "\n".join(
[node.get_content(metadata_mode=MetadataMode.LLM) for node in context_nodes]
)
res = await Settings.llm.astructured_predict(
output_cls=AnalysisDecision,
prompt=PromptTemplate(template=analyze_prompt),
user_request=user_request,
context_str=context_str,
conversation_context=conversation_context,
enhanced_prompt=enhanced_prompt,
)
return res
async def research(
question: str,
context_nodes: List[NodeWithScore],
) -> str:
prompt = """
You are a researcher who is in the process of answering the question.
The purpose is to answer the question based on the collected information, without using prior knowledge or making up any new information.
Always add citations to the sentence/point/paragraph using the id of the provided content.
The citation should follow this format: [citation:id]() where id is the id of the content.
E.g:
If we have a context like this:
<Citation id='abc-xyz'>
Baby llama is called cria
</Citation id='abc-xyz'>
And your answer uses the content, then the citation should be:
- Baby llama is called cria [citation:abc-xyz]()
Here is the provided context for the question:
<Collected information>
{context_str}
</Collected information>`
No prior knowledge, just use the provided context to answer the question: {question}
"""
context_str = "\n".join(
[_get_text_node_content_for_citation(node) for node in context_nodes]
)
res = await Settings.llm.acomplete(
prompt=prompt.format(question=question, context_str=context_str),
)
return res.text
async def write_report(
memory: SimpleComposableMemory,
user_request: str,
stream: bool = False,
) -> CompletionResponse | CompletionResponseAsyncGen:
report_prompt = """
You are a researcher writing a report based on a user request and the research context.
You have researched various perspectives related to the user request.
The report should provide a comprehensive outline covering all important points from the researched perspectives.
Create a well-structured outline for the research report that covers all the answers.
# IMPORTANT when writing in markdown format:
+ Use tables or figures where appropriate to enhance presentation.
+ Preserve all citation syntax (the `[citation:id]()` parts in the provided context). Keep these citations in the final report - no separate reference section is needed.
+ Do not add links, a table of contents, or a references section to the report.
<User request>
{user_request}
</User request>
<Research context>
{research_context}
</Research context>
Now, write a report addressing the user request based on the research provided following the format and guidelines above.
"""
research_context = "\n".join(
[f"{message.role}: {message.content}" for message in memory.get_all()]
)
llm_complete_func = (
Settings.llm.astream_complete if stream else Settings.llm.acomplete
)
res = await llm_complete_func(
prompt=report_prompt.format(
user_request=user_request,
research_context=research_context,
),
)
return res
def _get_text_node_content_for_citation(node: NodeWithScore) -> str:
"""
Construct node content for LLM with citation flag.
"""
node_id = node.node.node_id
content = f"<Citation id='{node_id}'>\n{node.get_content(metadata_mode=MetadataMode.LLM)}</Citation id='{node_id}'>"
return content
@@ -0,0 +1,328 @@
import logging
import os
import uuid
from typing import Any, Dict, List, Optional
from llama_index.core.indices.base import BaseIndex
from llama_index.core.memory import ChatMemoryBuffer
from llama_index.core.memory.simple_composable_memory import SimpleComposableMemory
from llama_index.core.schema import Node
from llama_index.core.types import ChatMessage, MessageRole
from llama_index.core.workflow import (
Context,
StartEvent,
StopEvent,
Workflow,
step,
)
from app.engine.index import IndexConfig, get_index
from app.workflows.agents import plan_research, research, write_report
from app.workflows.events import SourceNodesEvent
from app.workflows.models import (
CollectAnswersEvent,
DataEvent,
PlanResearchEvent,
ReportEvent,
ResearchEvent,
)
logger = logging.getLogger("uvicorn")
logger.setLevel(logging.INFO)
def create_workflow(
params: Optional[Dict[str, Any]] = None,
**kwargs,
) -> Workflow:
index_config = IndexConfig(**params)
index = get_index(index_config)
if index is None:
raise ValueError(
"Index is not found. Try run generation script to create the index first."
)
return DeepResearchWorkflow(
index=index,
timeout=120.0,
)
class DeepResearchWorkflow(Workflow):
"""
A workflow to research and analyze documents from multiple perspectives and write a comprehensive report.
Requirements:
- An indexed documents containing the knowledge base related to the topic
Steps:
1. Retrieve information from the knowledge base
2. Analyze the retrieved information and provide questions for answering
3. Answer the questions
4. Write the report based on the research results
"""
memory: SimpleComposableMemory
context_nodes: List[Node]
index: BaseIndex
user_request: str
stream: bool = True
def __init__(
self,
index: BaseIndex,
**kwargs,
):
super().__init__(**kwargs)
self.index = index
self.context_nodes = []
self.memory = SimpleComposableMemory.from_defaults(
primary_memory=ChatMemoryBuffer.from_defaults(),
)
@step
async def retrieve(self, ctx: Context, ev: StartEvent) -> PlanResearchEvent:
"""
Initiate the workflow: memory, tools, agent
"""
self.stream = ev.get("stream", True)
self.user_request = ev.get("user_msg")
chat_history = ev.get("chat_history")
if chat_history is not None:
self.memory.put_messages(chat_history)
await ctx.set("total_questions", 0)
# Add user message to memory
self.memory.put_messages(
messages=[
ChatMessage(
role=MessageRole.USER,
content=self.user_request,
)
]
)
ctx.write_event_to_stream(
DataEvent(
type="deep_research_event",
data={
"event": "retrieve",
"state": "inprogress",
},
)
)
retriever = self.index.as_retriever(
similarity_top_k=int(os.getenv("TOP_K", 10)),
)
nodes = retriever.retrieve(self.user_request)
self.context_nodes.extend(nodes)
ctx.write_event_to_stream(
DataEvent(
type="deep_research_event",
data={
"event": "retrieve",
"state": "done",
},
)
)
# Send source nodes to the stream
# Use SourceNodesEvent to display source nodes in the UI.
ctx.write_event_to_stream(
SourceNodesEvent(
nodes=nodes,
)
)
return PlanResearchEvent()
@step
async def analyze(
self, ctx: Context, ev: PlanResearchEvent
) -> ResearchEvent | ReportEvent | StopEvent:
"""
Analyze the retrieved information
"""
logger.info("Analyzing the retrieved information")
ctx.write_event_to_stream(
DataEvent(
type="deep_research_event",
data={
"event": "analyze",
"state": "inprogress",
},
)
)
total_questions = await ctx.get("total_questions")
res = await plan_research(
memory=self.memory,
context_nodes=self.context_nodes,
user_request=self.user_request,
total_questions=total_questions,
)
if res.decision == "cancel":
ctx.write_event_to_stream(
DataEvent(
type="deep_research_event",
data={
"event": "analyze",
"state": "done",
},
)
)
return StopEvent(
result=res.cancel_reason,
)
elif res.decision == "write":
# Writing a report without any research context is not allowed.
# It's a LLM hallucination.
if total_questions == 0:
ctx.write_event_to_stream(
DataEvent(
type="deep_research_event",
data={
"event": "analyze",
"state": "done",
},
)
)
return StopEvent(
result="Sorry, I have a problem when analyzing the retrieved information. Please try again.",
)
self.memory.put(
message=ChatMessage(
role=MessageRole.ASSISTANT,
content="No more idea to analyze. We should report the answers.",
)
)
ctx.send_event(ReportEvent())
else:
total_questions += len(res.research_questions)
await ctx.set("total_questions", total_questions) # For tracking
await ctx.set(
"waiting_questions", len(res.research_questions)
) # For waiting questions to be answered
self.memory.put(
message=ChatMessage(
role=MessageRole.ASSISTANT,
content="We need to find answers to the following questions:\n"
+ "\n".join(res.research_questions),
)
)
for question in res.research_questions:
question_id = str(uuid.uuid4())
ctx.write_event_to_stream(
DataEvent(
type="deep_research_event",
data={
"event": "answer",
"state": "pending",
"id": question_id,
"question": question,
"answer": None,
},
)
)
ctx.send_event(
ResearchEvent(
question_id=question_id,
question=question,
context_nodes=self.context_nodes,
)
)
ctx.write_event_to_stream(
DataEvent(
type="deep_research_event",
data={
"event": "analyze",
"state": "done",
},
)
)
return None
@step(num_workers=2)
async def answer(self, ctx: Context, ev: ResearchEvent) -> CollectAnswersEvent:
"""
Answer the question
"""
ctx.write_event_to_stream(
DataEvent(
type="deep_research_event",
data={
"event": "answer",
"state": "inprogress",
"id": ev.question_id,
"question": ev.question,
},
)
)
try:
answer = await research(
context_nodes=ev.context_nodes,
question=ev.question,
)
except Exception as e:
logger.error(f"Error answering question {ev.question}: {e}")
answer = f"Got error when answering the question: {ev.question}"
ctx.write_event_to_stream(
DataEvent(
type="deep_research_event",
data={
"event": "answer",
"state": "done",
"id": ev.question_id,
"question": ev.question,
"answer": answer,
},
)
)
return CollectAnswersEvent(
question_id=ev.question_id,
question=ev.question,
answer=answer,
)
@step
async def collect_answers(
self, ctx: Context, ev: CollectAnswersEvent
) -> PlanResearchEvent:
"""
Collect answers to all questions
"""
num_questions = await ctx.get("waiting_questions")
results = ctx.collect_events(
ev,
expected=[CollectAnswersEvent] * num_questions,
)
if results is None:
return None
for result in results:
self.memory.put(
message=ChatMessage(
role=MessageRole.ASSISTANT,
content=f"<Question>{result.question}</Question>\n<Answer>{result.answer}</Answer>",
)
)
await ctx.set("waiting_questions", 0)
self.memory.put(
message=ChatMessage(
role=MessageRole.ASSISTANT,
content="Researched all the questions. Now, i need to analyze if it's ready to write a report or need to research more.",
)
)
return PlanResearchEvent()
@step
async def report(self, ctx: Context, ev: ReportEvent) -> StopEvent:
"""
Report the answers
"""
res = await write_report(
memory=self.memory,
user_request=self.user_request,
stream=self.stream,
)
return StopEvent(
result=res,
)
@@ -0,0 +1,43 @@
from typing import List, Literal, Optional
from llama_index.core.schema import NodeWithScore
from llama_index.core.workflow import Event
from pydantic import BaseModel
# Workflow events
class PlanResearchEvent(Event):
pass
class ResearchEvent(Event):
question_id: str
question: str
context_nodes: List[NodeWithScore]
class CollectAnswersEvent(Event):
question_id: str
question: str
answer: str
class ReportEvent(Event):
pass
# Events that are streamed to the frontend and rendered there
class DeepResearchEventData(BaseModel):
event: Literal["retrieve", "analyze", "answer"]
state: Literal["pending", "inprogress", "done", "error"]
id: Optional[str] = None
question: Optional[str] = None
answer: Optional[str] = None
class DataEvent(Event):
type: Literal["deep_research_event"]
data: DeepResearchEventData
def to_response(self):
return self.model_dump()
@@ -0,0 +1,57 @@
This is a [LlamaIndex](https://www.llamaindex.ai/) multi-agents project using [Workflows](https://docs.llamaindex.ai/en/stable/understanding/workflows/).
## Getting Started
First, setup the environment with poetry:
> **_Note:_** This step is not needed if you are using the dev-container.
```shell
uv sync
```
Then check the parameters that have been pre-configured in the `.env` file in this directory. (E.g. you might need to configure an `OPENAI_API_KEY` if you're using OpenAI as model provider and `E2B_API_KEY` for the [E2B's code interpreter tool](https://e2b.dev/docs)).
Second, generate the embeddings of the documents in the `./data` directory:
```shell
uv run generate
```
Third, run the development server:
```shell
uv run dev
```
The example provides one streaming API endpoint `/api/chat`.
You can test the endpoint with the following curl request:
```
curl --location 'localhost:8000/api/chat' \
--header 'Content-Type: application/json' \
--data '{ "messages": [{ "role": "user", "content": "Create a report comparing the finances of Apple and Tesla" }] }'
```
You can start editing the API by modifying `app/api/routers/chat.py` or `app/workflows/financial_report.py`. The API auto-updates as you save the files.
Open [http://localhost:8000](http://localhost:8000) with your browser to start the app.
To start the app optimized for **production**, run:
```
uv run prod
```
## Deployments
For production deployments, check the [DEPLOY.md](DEPLOY.md) file.
## Learn More
To learn more about LlamaIndex, take a look at the following resources:
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex.
- [Workflows Introduction](https://docs.llamaindex.ai/en/stable/understanding/workflows/) - learn about LlamaIndex workflows.
You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome!
@@ -0,0 +1,3 @@
from .financial_report import create_workflow
__all__ = ["create_workflow"]

Some files were not shown because too many files have changed in this diff Show More