mirror of
https://github.com/run-llama/create-llama.git
synced 2026-07-16 03:04:21 -04:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 922e0ceb9b | |||
| ae1536768a | |||
| 78ded9e242 | |||
| 4f10840f04 | |||
| 9ca343c34e | |||
| ce2f24d73f | |||
| 65bc28d1f2 | |||
| 45e2eccb11 | |||
| b38adf894d | |||
| dd6f84fdd2 | |||
| 99e758fcbb | |||
| b0720ccd8c | |||
| 459824dbec | |||
| b08cad0123 | |||
| c7a978e0aa | |||
| 76aa33612c | |||
| e8db041d58 | |||
| b3f26856c4 | |||
| 26ab74bd9a | |||
| 17afc91850 | |||
| cbc996fb30 | |||
| bf1430d5b6 | |||
| e9a6cd049a | |||
| 60ed8fe080 | |||
| 56faee0b44 | |||
| abb7488895 | |||
| e851c0c834 | |||
| 58d9c0c400 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-llama": patch
|
||||
---
|
||||
|
||||
Remove UI question (use shadcn as default). Use `html` UI by calling create-llama with --ui html parameter
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-llama": patch
|
||||
---
|
||||
|
||||
Update loaders and tools config to yaml format
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-llama": patch
|
||||
---
|
||||
|
||||
Let user select multiple datasources (URLs, files and folders)
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-llama": patch
|
||||
---
|
||||
|
||||
Add Dockerfile template
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-llama": patch
|
||||
---
|
||||
|
||||
Merge non-streaming and streaming template to one
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-llama": patch
|
||||
---
|
||||
|
||||
Add support for agent generation for Typescript
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
matrix:
|
||||
node-version: [18, 20]
|
||||
python-version: ["3.11"]
|
||||
os: [macos-latest] #, windows-latest]
|
||||
os: [macos-latest, windows-latest]
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
run: pnpm run build
|
||||
working-directory: .
|
||||
- name: Install
|
||||
run: pnpm run install-local
|
||||
run: pnpm run pack-install
|
||||
working-directory: .
|
||||
- name: Run Playwright tests
|
||||
run: pnpm run e2e
|
||||
|
||||
@@ -45,3 +45,6 @@ e2e/cache
|
||||
|
||||
# intellij
|
||||
**/.idea
|
||||
|
||||
# build artifacts
|
||||
create-llama-*.tgz
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# create-llama
|
||||
|
||||
## 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
|
||||
|
||||
+28
-2
@@ -32,10 +32,10 @@ To install it, call:
|
||||
pnpm exec playwright install --with-deps
|
||||
```
|
||||
|
||||
Then you can first install the `create-llama` command locally:
|
||||
Then you can create a global `create-llama` command (used by the e2e tests) that is linked to your local dev environment (if you update the build, you don't need to re-link):
|
||||
|
||||
```
|
||||
pnpm run install-local
|
||||
pnpm link --global
|
||||
```
|
||||
|
||||
And then finally run the tests:
|
||||
@@ -45,3 +45,29 @@ pnpm run e2e
|
||||
```
|
||||
|
||||
To write new test cases write them in [e2e](/e2e)
|
||||
|
||||
## Changeset
|
||||
|
||||
We use [changesets](https://github.com/changesets/changesets) for managing versions and changelogs. To create a new changeset, run:
|
||||
|
||||
```
|
||||
pnpm changeset
|
||||
```
|
||||
|
||||
Please send a descriptive changeset for each PR.
|
||||
|
||||
## Publishing (maintainers only)
|
||||
|
||||
To publish a new version of the library, first create a new version:
|
||||
|
||||
```shell
|
||||
pnpm new-version
|
||||
```
|
||||
|
||||
If everything looks good, commit the generated files and release the new version:
|
||||
|
||||
```shell
|
||||
pnpm release
|
||||
git push # push to the main branch
|
||||
git push --tags
|
||||
```
|
||||
|
||||
+8
-8
@@ -26,11 +26,9 @@ export type InstallAppArgs = Omit<
|
||||
export async function createApp({
|
||||
template,
|
||||
framework,
|
||||
engine,
|
||||
ui,
|
||||
appPath,
|
||||
packageManager,
|
||||
eslint,
|
||||
frontend,
|
||||
openAiKey,
|
||||
llamaCloudKey,
|
||||
@@ -41,8 +39,9 @@ export async function createApp({
|
||||
vectorDb,
|
||||
externalPort,
|
||||
postInstallAction,
|
||||
dataSource,
|
||||
dataSources,
|
||||
tools,
|
||||
useLlamaParse,
|
||||
observability,
|
||||
}: InstallAppArgs): Promise<void> {
|
||||
const root = path.resolve(appPath);
|
||||
@@ -75,11 +74,9 @@ export async function createApp({
|
||||
root,
|
||||
template,
|
||||
framework,
|
||||
engine,
|
||||
ui,
|
||||
packageManager,
|
||||
isOnline,
|
||||
eslint,
|
||||
openAiKey,
|
||||
llamaCloudKey,
|
||||
model,
|
||||
@@ -89,8 +86,9 @@ export async function createApp({
|
||||
vectorDb,
|
||||
externalPort,
|
||||
postInstallAction,
|
||||
dataSource,
|
||||
dataSources,
|
||||
tools,
|
||||
useLlamaParse,
|
||||
observability,
|
||||
};
|
||||
|
||||
@@ -127,11 +125,13 @@ export async function createApp({
|
||||
}
|
||||
|
||||
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(
|
||||
"tools_config.json",
|
||||
`file://${root}/tools_config.json`,
|
||||
configFile,
|
||||
`file://${root}/${configFile}`,
|
||||
)} file.`,
|
||||
),
|
||||
);
|
||||
|
||||
+9
-25
@@ -4,7 +4,6 @@ import { ChildProcess } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import type {
|
||||
TemplateEngine,
|
||||
TemplateFramework,
|
||||
TemplatePostInstallAction,
|
||||
TemplateType,
|
||||
@@ -12,13 +11,13 @@ import type {
|
||||
} from "../helpers";
|
||||
import { createTestDir, runCreateLlama, type AppType } from "./utils";
|
||||
|
||||
const templateTypes: TemplateType[] = ["streaming", "simple"];
|
||||
const templateTypes: TemplateType[] = ["streaming"];
|
||||
const templateFrameworks: TemplateFramework[] = [
|
||||
"nextjs",
|
||||
"express",
|
||||
"fastapi",
|
||||
];
|
||||
const templateEngines: TemplateEngine[] = ["simple", "context"];
|
||||
const dataSources: string[] = ["--no-files", "--example-file"];
|
||||
const templateUIs: TemplateUI[] = ["shadcn", "html"];
|
||||
const templatePostInstallActions: TemplatePostInstallAction[] = [
|
||||
"none",
|
||||
@@ -27,24 +26,12 @@ const templatePostInstallActions: TemplatePostInstallAction[] = [
|
||||
|
||||
for (const templateType of templateTypes) {
|
||||
for (const templateFramework of templateFrameworks) {
|
||||
for (const templateEngine of templateEngines) {
|
||||
for (const dataSource of dataSources) {
|
||||
for (const templateUI of templateUIs) {
|
||||
for (const templatePostInstallAction of templatePostInstallActions) {
|
||||
if (templateFramework === "nextjs" && templateType === "simple") {
|
||||
// nextjs doesn't support simple templates - skip tests
|
||||
continue;
|
||||
}
|
||||
const appType: AppType =
|
||||
templateFramework === "express" || templateFramework === "fastapi"
|
||||
? templateType === "simple"
|
||||
? "--no-frontend" // simple templates don't have frontends
|
||||
: "--frontend"
|
||||
: "";
|
||||
if (appType === "--no-frontend" && templateUI !== "html") {
|
||||
// if there's no frontend, don't iterate over UIs
|
||||
continue;
|
||||
}
|
||||
test.describe(`try create-llama ${templateType} ${templateFramework} ${templateEngine} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
|
||||
templateFramework === "nextjs" ? "" : "--frontend";
|
||||
test.describe(`try create-llama ${templateType} ${templateFramework} ${dataSource} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
|
||||
let port: number;
|
||||
let externalPort: number;
|
||||
let cwd: string;
|
||||
@@ -61,7 +48,7 @@ for (const templateType of templateTypes) {
|
||||
cwd,
|
||||
templateType,
|
||||
templateFramework,
|
||||
templateEngine,
|
||||
dataSource,
|
||||
templateUI,
|
||||
vectorDb,
|
||||
appType,
|
||||
@@ -79,7 +66,6 @@ for (const templateType of templateTypes) {
|
||||
});
|
||||
test("Frontend should have a title", async ({ page }) => {
|
||||
test.skip(templatePostInstallAction !== "runApp");
|
||||
test.skip(appType === "--no-frontend");
|
||||
await page.goto(`http://localhost:${port}`);
|
||||
await expect(page.getByText("Built by LlamaIndex")).toBeVisible();
|
||||
});
|
||||
@@ -88,7 +74,6 @@ for (const templateType of templateTypes) {
|
||||
page,
|
||||
}) => {
|
||||
test.skip(templatePostInstallAction !== "runApp");
|
||||
test.skip(appType === "--no-frontend");
|
||||
await page.goto(`http://localhost:${port}`);
|
||||
await page.fill("form input", "hello");
|
||||
const [response] = await Promise.all([
|
||||
@@ -109,14 +94,13 @@ for (const templateType of templateTypes) {
|
||||
expect(response.ok()).toBeTruthy();
|
||||
});
|
||||
|
||||
test("Backend should response when calling API", async ({
|
||||
test("Backend frameworks should response when calling non-streaming chat API", async ({
|
||||
request,
|
||||
}) => {
|
||||
test.skip(templatePostInstallAction !== "runApp");
|
||||
test.skip(appType !== "--no-frontend");
|
||||
const backendPort = appType === "" ? port : externalPort;
|
||||
test.skip(templateFramework === "nextjs");
|
||||
const response = await request.post(
|
||||
`http://localhost:${backendPort}/api/chat`,
|
||||
`http://localhost:${externalPort}/api/chat/request`,
|
||||
{
|
||||
data: {
|
||||
messages: [
|
||||
|
||||
+7
-7
@@ -4,7 +4,6 @@ import { mkdir } from "node:fs/promises";
|
||||
import * as path from "path";
|
||||
import waitPort from "wait-port";
|
||||
import {
|
||||
TemplateEngine,
|
||||
TemplateFramework,
|
||||
TemplatePostInstallAction,
|
||||
TemplateType,
|
||||
@@ -67,7 +66,7 @@ export async function runCreateLlama(
|
||||
cwd: string,
|
||||
templateType: TemplateType,
|
||||
templateFramework: TemplateFramework,
|
||||
templateEngine: TemplateEngine,
|
||||
dataSource: string,
|
||||
templateUI: TemplateUI,
|
||||
vectorDb: TemplateVectorDB,
|
||||
appType: AppType,
|
||||
@@ -75,10 +74,13 @@ export async function runCreateLlama(
|
||||
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,
|
||||
templateEngine,
|
||||
dataSource,
|
||||
templateUI,
|
||||
appType,
|
||||
].join("-");
|
||||
@@ -89,8 +91,7 @@ export async function runCreateLlama(
|
||||
templateType,
|
||||
"--framework",
|
||||
templateFramework,
|
||||
"--engine",
|
||||
templateEngine,
|
||||
dataSource,
|
||||
"--ui",
|
||||
templateUI,
|
||||
"--vector-db",
|
||||
@@ -100,9 +101,8 @@ export async function runCreateLlama(
|
||||
"--embedding-model",
|
||||
EMBEDDING_MODEL,
|
||||
"--open-ai-key",
|
||||
process.env.OPENAI_API_KEY || "testKey",
|
||||
process.env.OPENAI_API_KEY,
|
||||
appType,
|
||||
"--eslint",
|
||||
"--use-pnpm",
|
||||
"--port",
|
||||
port,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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 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;
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import {
|
||||
FileSourceConfig,
|
||||
TemplateDataSource,
|
||||
TemplateFramework,
|
||||
TemplateVectorDB,
|
||||
@@ -99,44 +98,6 @@ const getVectorDBEnvs = (vectorDb: TemplateVectorDB) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getDataSourceEnvs = (
|
||||
dataSource: TemplateDataSource,
|
||||
llamaCloudKey?: string,
|
||||
) => {
|
||||
switch (dataSource.type) {
|
||||
case "web":
|
||||
return [
|
||||
{
|
||||
name: "BASE_URL",
|
||||
description: "The base URL to start web scraping.",
|
||||
},
|
||||
{
|
||||
name: "URL_PREFIX",
|
||||
description: "The prefix of the URL to start web scraping.",
|
||||
},
|
||||
{
|
||||
name: "MAX_DEPTH",
|
||||
description: "The maximum depth to scrape.",
|
||||
},
|
||||
];
|
||||
case "file":
|
||||
case "folder":
|
||||
return [
|
||||
...((dataSource?.config as FileSourceConfig).useLlamaParse
|
||||
? [
|
||||
{
|
||||
name: "LLAMA_CLOUD_API_KEY",
|
||||
description: `The Llama Cloud API key.`,
|
||||
value: llamaCloudKey,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const createBackendEnvFile = async (
|
||||
root: string,
|
||||
opts: {
|
||||
@@ -146,7 +107,7 @@ export const createBackendEnvFile = async (
|
||||
model?: string;
|
||||
embeddingModel?: string;
|
||||
framework?: TemplateFramework;
|
||||
dataSource?: TemplateDataSource;
|
||||
dataSources?: TemplateDataSource[];
|
||||
port?: number;
|
||||
},
|
||||
) => {
|
||||
@@ -165,13 +126,13 @@ export const createBackendEnvFile = async (
|
||||
description: "The OpenAI API key to use.",
|
||||
value: opts.openAiKey,
|
||||
},
|
||||
|
||||
{
|
||||
name: "LLAMA_CLOUD_API_KEY",
|
||||
description: `The Llama Cloud API key.`,
|
||||
value: opts.llamaCloudKey,
|
||||
},
|
||||
// Add vector database environment variables
|
||||
...(opts.vectorDb ? getVectorDBEnvs(opts.vectorDb) : []),
|
||||
// Add data source environment variables
|
||||
...(opts.dataSource
|
||||
? getDataSourceEnvs(opts.dataSource, opts.llamaCloudKey)
|
||||
: []),
|
||||
];
|
||||
let envVars: EnvVar[] = [];
|
||||
if (opts.framework === "fastapi") {
|
||||
|
||||
+20
-41
@@ -1,11 +1,9 @@
|
||||
import { copy } from "./copy";
|
||||
import { callPackageManager } from "./install";
|
||||
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { cyan } from "picocolors";
|
||||
|
||||
import { templatesDir } from "./dir";
|
||||
import fsExtra from "fs-extra";
|
||||
import { createBackendEnvFile, createFrontendEnvFile } from "./env-variables";
|
||||
import { PackageManager } from "./get-pkg-manager";
|
||||
import { installLlamapackProject } from "./llama-pack";
|
||||
@@ -27,8 +25,8 @@ async function generateContextData(
|
||||
packageManager?: PackageManager,
|
||||
openAiKey?: string,
|
||||
vectorDb?: TemplateVectorDB,
|
||||
dataSource?: TemplateDataSource,
|
||||
llamaCloudKey?: string,
|
||||
useLlamaParse?: boolean,
|
||||
) {
|
||||
if (packageManager) {
|
||||
const runGenerate = `${cyan(
|
||||
@@ -37,8 +35,7 @@ async function generateContextData(
|
||||
: `${packageManager} run generate`,
|
||||
)}`;
|
||||
const openAiKeyConfigured = openAiKey || process.env["OPENAI_API_KEY"];
|
||||
const llamaCloudKeyConfigured = (dataSource?.config as FileSourceConfig)
|
||||
?.useLlamaParse
|
||||
const llamaCloudKeyConfigured = useLlamaParse
|
||||
? llamaCloudKey || process.env["LLAMA_CLOUD_API_KEY"]
|
||||
: true;
|
||||
const hasVectorDb = vectorDb && vectorDb !== "none";
|
||||
@@ -76,38 +73,16 @@ async function generateContextData(
|
||||
|
||||
const copyContextData = async (
|
||||
root: string,
|
||||
dataSource?: TemplateDataSource,
|
||||
dataSources: TemplateDataSource[],
|
||||
) => {
|
||||
const destPath = path.join(root, "data");
|
||||
for (const dataSource of dataSources) {
|
||||
const dataSourceConfig = dataSource?.config as FileSourceConfig;
|
||||
// Copy local data
|
||||
const dataPath = dataSourceConfig.path;
|
||||
|
||||
const dataSourceConfig = dataSource?.config as FileSourceConfig;
|
||||
|
||||
// Copy file
|
||||
if (dataSource?.type === "file") {
|
||||
if (dataSourceConfig.path) {
|
||||
console.log(`\nCopying file to ${cyan(destPath)}\n`);
|
||||
await fs.mkdir(destPath, { recursive: true });
|
||||
await fs.copyFile(
|
||||
dataSourceConfig.path,
|
||||
path.join(destPath, path.basename(dataSourceConfig.path)),
|
||||
);
|
||||
} else {
|
||||
console.log("Missing file path in config");
|
||||
process.exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy folder
|
||||
if (dataSource?.type === "folder") {
|
||||
const srcPath =
|
||||
dataSourceConfig.path ?? path.join(templatesDir, "components", "data");
|
||||
console.log(`\nCopying data to ${cyan(destPath)}\n`);
|
||||
await copy("**", destPath, {
|
||||
parents: true,
|
||||
cwd: srcPath,
|
||||
});
|
||||
return;
|
||||
const destPath = path.join(root, "data", path.basename(dataPath));
|
||||
console.log("Copying data from path:", dataPath);
|
||||
await fsExtra.copy(dataPath, destPath);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -157,12 +132,16 @@ export const installTemplate = async (
|
||||
model: props.model,
|
||||
embeddingModel: props.embeddingModel,
|
||||
framework: props.framework,
|
||||
dataSource: props.dataSource,
|
||||
dataSources: props.dataSources,
|
||||
port: props.externalPort,
|
||||
});
|
||||
|
||||
if (props.engine === "context") {
|
||||
await copyContextData(props.root, props.dataSource);
|
||||
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"
|
||||
@@ -172,14 +151,14 @@ export const installTemplate = async (
|
||||
props.packageManager,
|
||||
props.openAiKey,
|
||||
props.vectorDb,
|
||||
props.dataSource,
|
||||
props.llamaCloudKey,
|
||||
props.useLlamaParse,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// this is a frontend for a full-stack app, create .env file with model information
|
||||
createFrontendEnvFile(props.root, {
|
||||
await createFrontendEnvFile(props.root, {
|
||||
model: props.model,
|
||||
customApiPath: props.customApiPath,
|
||||
});
|
||||
|
||||
+83
-31
@@ -3,15 +3,16 @@ import path from "path";
|
||||
import { cyan, red } from "picocolors";
|
||||
import { parse, stringify } from "smol-toml";
|
||||
import terminalLink from "terminal-link";
|
||||
import yaml, { Document } from "yaml";
|
||||
import { copy } from "./copy";
|
||||
import { templatesDir } from "./dir";
|
||||
import { isPoetryAvailable, tryPoetryInstall } from "./poetry";
|
||||
import { Tool } from "./tools";
|
||||
import {
|
||||
FileSourceConfig,
|
||||
InstallTemplateArgs,
|
||||
TemplateDataSource,
|
||||
TemplateVectorDB,
|
||||
WebSourceConfig,
|
||||
} from "./types";
|
||||
|
||||
interface Dependency {
|
||||
@@ -54,13 +55,17 @@ const getAdditionalDependencies = (
|
||||
name: "llama-index-vector-stores-milvus",
|
||||
version: "^0.1.6",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "pymilvus",
|
||||
version: "2.3.7",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add data source dependencies
|
||||
const dataSourceType = dataSource?.type;
|
||||
if (dataSourceType === "file" || dataSourceType === "folder") {
|
||||
if (dataSourceType === "file") {
|
||||
// llama-index-readers-file (pdf, excel, csv) is already included in llama_index package
|
||||
dependencies.push({
|
||||
name: "docx2txt",
|
||||
@@ -173,20 +178,20 @@ export const installPythonTemplate = async ({
|
||||
root,
|
||||
template,
|
||||
framework,
|
||||
engine,
|
||||
vectorDb,
|
||||
dataSource,
|
||||
dataSources,
|
||||
tools,
|
||||
postInstallAction,
|
||||
useLlamaParse,
|
||||
}: Pick<
|
||||
InstallTemplateArgs,
|
||||
| "root"
|
||||
| "framework"
|
||||
| "template"
|
||||
| "engine"
|
||||
| "vectorDb"
|
||||
| "dataSource"
|
||||
| "dataSources"
|
||||
| "tools"
|
||||
| "useLlamaParse"
|
||||
| "postInstallAction"
|
||||
>) => {
|
||||
console.log("\nInitializing Python project with template:", template, "\n");
|
||||
@@ -211,9 +216,10 @@ export const installPythonTemplate = async ({
|
||||
},
|
||||
});
|
||||
|
||||
if (engine === "context") {
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
|
||||
if (dataSources.length > 0) {
|
||||
const enginePath = path.join(root, "app", "engine");
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
|
||||
const vectorDbDirName = vectorDb ?? "none";
|
||||
const VectorDBPath = path.join(
|
||||
@@ -233,16 +239,14 @@ export const installPythonTemplate = async ({
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "python", "agent"),
|
||||
});
|
||||
// Write tools_config.json
|
||||
// Write tool configs
|
||||
const configContent: Record<string, any> = {};
|
||||
tools.forEach((tool) => {
|
||||
configContent[tool.name] = tool.config ?? {};
|
||||
});
|
||||
const configFilePath = path.join(root, "tools_config.json");
|
||||
await fs.writeFile(
|
||||
configFilePath,
|
||||
JSON.stringify(configContent, null, 2),
|
||||
);
|
||||
const configFilePath = path.join(root, "config/tools.yaml");
|
||||
await fs.mkdir(path.join(root, "config"), { recursive: true });
|
||||
await fs.writeFile(configFilePath, yaml.stringify(configContent));
|
||||
} else {
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
@@ -250,30 +254,78 @@ export const installPythonTemplate = async ({
|
||||
});
|
||||
}
|
||||
|
||||
const dataSourceType = dataSource?.type;
|
||||
if (dataSourceType !== undefined && dataSourceType !== "none") {
|
||||
let loaderFolder: string;
|
||||
if (dataSourceType === "file" || dataSourceType === "folder") {
|
||||
const dataSourceConfig = dataSource?.config as FileSourceConfig;
|
||||
loaderFolder = dataSourceConfig.useLlamaParse ? "llama_parse" : "file";
|
||||
} else {
|
||||
loaderFolder = dataSourceType;
|
||||
}
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "loaders", "python", loaderFolder),
|
||||
const loaderConfig = new Document({});
|
||||
const loaderPath = path.join(enginePath, "loaders");
|
||||
|
||||
// Copy loaders to enginePath
|
||||
await copy("**", loaderPath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "loaders", "python"),
|
||||
});
|
||||
|
||||
// Generate loaders config
|
||||
// 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);
|
||||
}
|
||||
// Write loaders config
|
||||
if (Object.keys(loaderConfig).length > 0) {
|
||||
const loaderConfigPath = path.join(root, "config/loaders.yaml");
|
||||
await fs.mkdir(path.join(root, "config"), { recursive: true });
|
||||
await fs.writeFile(loaderConfigPath, yaml.stringify(loaderConfig));
|
||||
}
|
||||
}
|
||||
|
||||
const addOnDependencies = getAdditionalDependencies(
|
||||
vectorDb,
|
||||
dataSource,
|
||||
tools,
|
||||
);
|
||||
const addOnDependencies = dataSources
|
||||
.map((ds) => getAdditionalDependencies(vectorDb, ds, tools))
|
||||
.flat();
|
||||
await addDependencies(root, addOnDependencies);
|
||||
|
||||
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
|
||||
installPythonDependencies();
|
||||
}
|
||||
|
||||
// Copy deployment files for python
|
||||
await copy("**", root, {
|
||||
cwd: path.join(compPath, "deployments", "python"),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { red } from "picocolors";
|
||||
import { TemplateFramework } from "./types";
|
||||
|
||||
export type Tool = {
|
||||
display: string;
|
||||
name: string;
|
||||
config?: Record<string, any>;
|
||||
dependencies?: ToolDependencies[];
|
||||
supportedFrameworks?: Array<TemplateFramework>;
|
||||
};
|
||||
export type ToolDependencies = {
|
||||
name: string;
|
||||
@@ -27,6 +29,7 @@ export const supportedTools: Tool[] = [
|
||||
version: "0.1.2",
|
||||
},
|
||||
],
|
||||
supportedFrameworks: ["fastapi"],
|
||||
},
|
||||
{
|
||||
display: "Wikipedia",
|
||||
@@ -37,6 +40,7 @@ export const supportedTools: Tool[] = [
|
||||
version: "0.1.2",
|
||||
},
|
||||
],
|
||||
supportedFrameworks: ["fastapi", "express", "nextjs"],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
+7
-8
@@ -1,9 +1,8 @@
|
||||
import { PackageManager } from "../helpers/get-pkg-manager";
|
||||
import { Tool } from "./tools";
|
||||
|
||||
export type TemplateType = "simple" | "streaming" | "community" | "llamapack";
|
||||
export type TemplateType = "streaming" | "community" | "llamapack";
|
||||
export type TemplateFramework = "nextjs" | "express" | "fastapi";
|
||||
export type TemplateEngine = "simple" | "context";
|
||||
export type TemplateUI = "html" | "shadcn";
|
||||
export type TemplateVectorDB = "none" | "mongo" | "pg" | "pinecone" | "milvus";
|
||||
export type TemplatePostInstallAction =
|
||||
@@ -15,17 +14,18 @@ export type TemplateDataSource = {
|
||||
type: TemplateDataSourceType;
|
||||
config: TemplateDataSourceConfig;
|
||||
};
|
||||
export type TemplateDataSourceType = "none" | "file" | "folder" | "web";
|
||||
export type TemplateDataSourceType = "file" | "web";
|
||||
export type TemplateObservability = "none" | "opentelemetry";
|
||||
// Config for both file and folder
|
||||
export type FileSourceConfig = {
|
||||
path?: string;
|
||||
useLlamaParse?: boolean;
|
||||
path: string;
|
||||
};
|
||||
export type WebSourceConfig = {
|
||||
baseUrl?: string;
|
||||
prefix?: string;
|
||||
depth?: number;
|
||||
};
|
||||
|
||||
export type TemplateDataSourceConfig = FileSourceConfig | WebSourceConfig;
|
||||
|
||||
export type CommunityProjectConfig = {
|
||||
@@ -42,13 +42,12 @@ export interface InstallTemplateArgs {
|
||||
isOnline: boolean;
|
||||
template: TemplateType;
|
||||
framework: TemplateFramework;
|
||||
engine: TemplateEngine;
|
||||
ui: TemplateUI;
|
||||
dataSource?: TemplateDataSource;
|
||||
eslint: boolean;
|
||||
dataSources: TemplateDataSource[];
|
||||
customApiPath?: string;
|
||||
openAiKey?: string;
|
||||
llamaCloudKey?: string;
|
||||
useLlamaParse?: boolean;
|
||||
model: string;
|
||||
embeddingModel: string;
|
||||
communityProjectConfig?: CommunityProjectConfig;
|
||||
|
||||
+63
-53
@@ -6,7 +6,8 @@ import { copy } from "../helpers/copy";
|
||||
import { callPackageManager } from "../helpers/install";
|
||||
import { templatesDir } from "./dir";
|
||||
import { PackageManager } from "./get-pkg-manager";
|
||||
import { FileSourceConfig, InstallTemplateArgs } from "./types";
|
||||
import { makeDir } from "./make-dir";
|
||||
import { InstallTemplateArgs } from "./types";
|
||||
|
||||
const rename = (name: string) => {
|
||||
switch (name) {
|
||||
@@ -56,15 +57,15 @@ export const installTSTemplate = async ({
|
||||
isOnline,
|
||||
template,
|
||||
framework,
|
||||
engine,
|
||||
ui,
|
||||
eslint,
|
||||
customApiPath,
|
||||
vectorDb,
|
||||
postInstallAction,
|
||||
backend,
|
||||
observability,
|
||||
dataSource,
|
||||
tools,
|
||||
dataSources,
|
||||
useLlamaParse,
|
||||
}: InstallTemplateArgs & { backend: boolean }) => {
|
||||
console.log(bold(`Using ${packageManager}.`));
|
||||
|
||||
@@ -74,7 +75,6 @@ export const installTSTemplate = async ({
|
||||
console.log("\nInitializing project with template:", template, "\n");
|
||||
const templatePath = path.join(templatesDir, "types", template, framework);
|
||||
const copySource = ["**"];
|
||||
if (!eslint) copySource.push("!eslintrc.json");
|
||||
|
||||
await copy(copySource, root, {
|
||||
parents: true,
|
||||
@@ -140,50 +140,63 @@ export const installTSTemplate = async ({
|
||||
/**
|
||||
* Copy the selected chat engine files to the target directory and reference it.
|
||||
*/
|
||||
let relativeEngineDestPath;
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
if (engine && (framework === "express" || framework === "nextjs")) {
|
||||
console.log("\nUsing chat engine:", engine, "\n");
|
||||
const relativeEngineDestPath =
|
||||
framework === "nextjs"
|
||||
? path.join("app", "api", "chat")
|
||||
: path.join("src", "controllers");
|
||||
const enginePath = path.join(root, relativeEngineDestPath, "engine");
|
||||
|
||||
let vectorDBFolder: string = engine;
|
||||
|
||||
if (engine !== "simple" && vectorDb) {
|
||||
if (dataSources.length === 0) {
|
||||
// use simple hat engine if user neither select tools nor a data source
|
||||
console.log("\nUsing simple chat engine\n");
|
||||
} else {
|
||||
if (vectorDb) {
|
||||
// copy vector db component
|
||||
console.log("\nUsing vector DB:", vectorDb, "\n");
|
||||
vectorDBFolder = vectorDb;
|
||||
}
|
||||
|
||||
relativeEngineDestPath =
|
||||
framework === "nextjs"
|
||||
? path.join("app", "api", "chat")
|
||||
: path.join("src", "controllers");
|
||||
|
||||
const enginePath = path.join(root, relativeEngineDestPath, "engine");
|
||||
|
||||
// copy vector db component
|
||||
const vectorDBPath = path.join(
|
||||
compPath,
|
||||
"vectordbs",
|
||||
"typescript",
|
||||
vectorDBFolder,
|
||||
);
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: vectorDBPath,
|
||||
});
|
||||
|
||||
// copy loader component
|
||||
const dataSourceType = dataSource?.type;
|
||||
if (dataSourceType && dataSourceType !== "none") {
|
||||
let loaderFolder: string;
|
||||
if (dataSourceType === "file" || dataSourceType === "folder") {
|
||||
const dataSourceConfig = dataSource?.config as FileSourceConfig;
|
||||
loaderFolder = dataSourceConfig.useLlamaParse ? "llama_parse" : "file";
|
||||
} else {
|
||||
loaderFolder = dataSourceType;
|
||||
}
|
||||
const vectorDBPath = path.join(
|
||||
compPath,
|
||||
"vectordbs",
|
||||
"typescript",
|
||||
vectorDb,
|
||||
);
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "loaders", "typescript", loaderFolder),
|
||||
cwd: vectorDBPath,
|
||||
});
|
||||
}
|
||||
// copy loader component (TS only supports llama_parse and file for now)
|
||||
let loaderFolder: string;
|
||||
loaderFolder = useLlamaParse ? "llama_parse" : "file";
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "loaders", "typescript", loaderFolder),
|
||||
});
|
||||
if (tools?.length) {
|
||||
// use agent chat engine if user selects tools
|
||||
console.log("\nUsing agent chat engine\n");
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "typescript", "agent"),
|
||||
});
|
||||
|
||||
// Write config/tools.json
|
||||
const configContent: Record<string, any> = {};
|
||||
tools.forEach((tool) => {
|
||||
configContent[tool.name] = tool.config ?? {};
|
||||
});
|
||||
const configPath = path.join(root, "config");
|
||||
await makeDir(configPath);
|
||||
await fs.writeFile(
|
||||
path.join(configPath, "tools.json"),
|
||||
JSON.stringify(configContent, null, 2),
|
||||
);
|
||||
} else {
|
||||
// use context chat engine if user does not select tools
|
||||
console.log("\nUsing context chat engine\n");
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "typescript", "chat"),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -227,7 +240,7 @@ export const installTSTemplate = async ({
|
||||
// modify the dev script to use the custom api path
|
||||
}
|
||||
|
||||
if (engine === "context" && relativeEngineDestPath) {
|
||||
if (dataSources.length > 0 && relativeEngineDestPath) {
|
||||
// add generate script if using context engine
|
||||
packageJson.scripts = {
|
||||
...packageJson.scripts,
|
||||
@@ -274,14 +287,6 @@ export const installTSTemplate = async ({
|
||||
};
|
||||
}
|
||||
|
||||
if (!eslint) {
|
||||
// Remove packages starting with "eslint" from devDependencies
|
||||
packageJson.devDependencies = Object.fromEntries(
|
||||
Object.entries(packageJson.devDependencies).filter(
|
||||
([key]) => !key.startsWith("eslint"),
|
||||
),
|
||||
);
|
||||
}
|
||||
await fs.writeFile(
|
||||
packageJsonFile,
|
||||
JSON.stringify(packageJson, null, 2) + os.EOL,
|
||||
@@ -290,4 +295,9 @@ export const installTSTemplate = async ({
|
||||
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
|
||||
await installTSDependencies(packageJson, packageManager, isOnline);
|
||||
}
|
||||
|
||||
// Copy deployment files for typescript
|
||||
await copy("**", root, {
|
||||
cwd: path.join(compPath, "deployments", "typescript"),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { execSync } from "child_process";
|
||||
import Commander from "commander";
|
||||
@@ -10,6 +9,7 @@ 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 { runApp } from "./helpers/run-app";
|
||||
@@ -32,13 +32,6 @@ const program = new Commander.Command(packageJson.name)
|
||||
.action((name) => {
|
||||
projectPath = name;
|
||||
})
|
||||
.option(
|
||||
"--eslint",
|
||||
`
|
||||
|
||||
Initialize with eslint config.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--use-npm",
|
||||
`
|
||||
@@ -72,13 +65,6 @@ const program = new Commander.Command(packageJson.name)
|
||||
`
|
||||
|
||||
Select a template to bootstrap the application with.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--engine <engine>",
|
||||
`
|
||||
|
||||
Select a chat engine to bootstrap the application with.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
@@ -93,6 +79,13 @@ const program = new Commander.Command(packageJson.name)
|
||||
`
|
||||
|
||||
Specify the path to a local file or folder for chatting.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--example-file",
|
||||
`
|
||||
|
||||
Select to use an example PDF as data source.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
@@ -165,7 +158,7 @@ const program = new Commander.Command(packageJson.name)
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--llama-parse",
|
||||
"--use-llama-parse",
|
||||
`
|
||||
Enable LlamaParse.
|
||||
`,
|
||||
@@ -189,9 +182,6 @@ const program = new Commander.Command(packageJson.name)
|
||||
if (process.argv.includes("--no-frontend")) {
|
||||
program.frontend = false;
|
||||
}
|
||||
if (process.argv.includes("--no-eslint")) {
|
||||
program.eslint = false;
|
||||
}
|
||||
if (process.argv.includes("--tools")) {
|
||||
if (program.tools === "none") {
|
||||
program.tools = [];
|
||||
@@ -200,7 +190,12 @@ if (process.argv.includes("--tools")) {
|
||||
}
|
||||
}
|
||||
if (process.argv.includes("--no-llama-parse")) {
|
||||
program.llamaParse = false;
|
||||
program.useLlamaParse = false;
|
||||
}
|
||||
if (process.argv.includes("--no-files")) {
|
||||
program.dataSources = [];
|
||||
} else {
|
||||
program.dataSources = getDataSources(program.files, program.exampleFile);
|
||||
}
|
||||
|
||||
const packageManager = !!program.useNpm
|
||||
@@ -288,11 +283,9 @@ async function run(): Promise<void> {
|
||||
await createApp({
|
||||
template: program.template,
|
||||
framework: program.framework,
|
||||
engine: program.engine,
|
||||
ui: program.ui,
|
||||
appPath: resolvedProjectPath,
|
||||
packageManager,
|
||||
eslint: program.eslint,
|
||||
frontend: program.frontend,
|
||||
openAiKey: program.openAiKey,
|
||||
llamaCloudKey: program.llamaCloudKey,
|
||||
@@ -303,8 +296,9 @@ async function run(): Promise<void> {
|
||||
vectorDb: program.vectorDb,
|
||||
externalPort: program.externalPort,
|
||||
postInstallAction: program.postInstallAction,
|
||||
dataSource: program.dataSource,
|
||||
dataSources: program.dataSources,
|
||||
tools: program.tools,
|
||||
useLlamaParse: program.useLlamaParse,
|
||||
observability: program.observability,
|
||||
});
|
||||
conf.set("preferences", preferences);
|
||||
|
||||
+11
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-llama",
|
||||
"version": "0.0.30",
|
||||
"version": "0.0.31",
|
||||
"keywords": [
|
||||
"rag",
|
||||
"llamaindex",
|
||||
@@ -24,13 +24,16 @@
|
||||
"format": "prettier --ignore-unknown --cache --check .",
|
||||
"format:write": "prettier --ignore-unknown --write .",
|
||||
"dev": "ncc build ./index.ts -w -o dist/",
|
||||
"build": "npm run clean && ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
|
||||
"build": "bash ./scripts/build.sh",
|
||||
"build:ncc": "pnpm run clean && ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
|
||||
"lint": "eslint . --ignore-pattern dist --ignore-pattern e2e/cache",
|
||||
"e2e": "playwright test",
|
||||
"prepare": "husky",
|
||||
"install-local": "pnpm link --global",
|
||||
"release": "pnpm run build && changeset publish",
|
||||
"new-version": "pnpm run build && changeset version"
|
||||
"new-version": "pnpm run build && changeset version",
|
||||
"release-snapshot": "pnpm run build && changeset publish --tag snapshot",
|
||||
"new-snapshot": "pnpm run build && changeset version --snapshot",
|
||||
"pack-install": "bash ./scripts/pack.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.41.1",
|
||||
@@ -41,6 +44,7 @@
|
||||
"@types/prompts": "2.0.1",
|
||||
"@types/tar": "6.1.5",
|
||||
"@types/validate-npm-package-name": "3.0.0",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"@vercel/ncc": "0.38.1",
|
||||
"async-retry": "1.3.1",
|
||||
"async-sema": "3.0.1",
|
||||
@@ -66,7 +70,9 @@
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"typescript": "^5.3.3",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"ora": "^8.0.1"
|
||||
"ora": "^8.0.1",
|
||||
"fs-extra": "11.2.0",
|
||||
"yaml": "2.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14.0"
|
||||
|
||||
Generated
+50
@@ -20,6 +20,9 @@ devDependencies:
|
||||
'@types/cross-spawn':
|
||||
specifier: 6.0.0
|
||||
version: 6.0.0
|
||||
'@types/fs-extra':
|
||||
specifier: 11.0.4
|
||||
version: 11.0.4
|
||||
'@types/node':
|
||||
specifier: ^20.11.7
|
||||
version: 20.11.26
|
||||
@@ -62,6 +65,9 @@ devDependencies:
|
||||
fast-glob:
|
||||
specifier: 3.3.1
|
||||
version: 3.3.1
|
||||
fs-extra:
|
||||
specifier: 11.2.0
|
||||
version: 11.2.0
|
||||
got:
|
||||
specifier: 10.7.0
|
||||
version: 10.7.0
|
||||
@@ -107,6 +113,9 @@ devDependencies:
|
||||
wait-port:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
yaml:
|
||||
specifier: 2.4.1
|
||||
version: 2.4.1
|
||||
|
||||
packages:
|
||||
|
||||
@@ -489,10 +498,23 @@ packages:
|
||||
'@types/node': 20.11.26
|
||||
dev: true
|
||||
|
||||
/@types/fs-extra@11.0.4:
|
||||
resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==}
|
||||
dependencies:
|
||||
'@types/jsonfile': 6.1.4
|
||||
'@types/node': 20.11.26
|
||||
dev: true
|
||||
|
||||
/@types/http-cache-semantics@4.0.4:
|
||||
resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
|
||||
dev: true
|
||||
|
||||
/@types/jsonfile@6.1.4:
|
||||
resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==}
|
||||
dependencies:
|
||||
'@types/node': 20.11.26
|
||||
dev: true
|
||||
|
||||
/@types/keyv@3.1.4:
|
||||
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
|
||||
dependencies:
|
||||
@@ -1437,6 +1459,15 @@ packages:
|
||||
signal-exit: 4.1.0
|
||||
dev: true
|
||||
|
||||
/fs-extra@11.2.0:
|
||||
resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
|
||||
engines: {node: '>=14.14'}
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
jsonfile: 6.1.0
|
||||
universalify: 2.0.1
|
||||
dev: true
|
||||
|
||||
/fs-extra@7.0.1:
|
||||
resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
|
||||
engines: {node: '>=6 <7 || >=8'}
|
||||
@@ -1982,6 +2013,14 @@ packages:
|
||||
graceful-fs: 4.2.11
|
||||
dev: true
|
||||
|
||||
/jsonfile@6.1.0:
|
||||
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
|
||||
dependencies:
|
||||
universalify: 2.0.1
|
||||
optionalDependencies:
|
||||
graceful-fs: 4.2.11
|
||||
dev: true
|
||||
|
||||
/keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
dependencies:
|
||||
@@ -3185,6 +3224,11 @@ packages:
|
||||
engines: {node: '>= 4.0.0'}
|
||||
dev: true
|
||||
|
||||
/universalify@2.0.1:
|
||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
dev: true
|
||||
|
||||
/update-check@1.5.4:
|
||||
resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==}
|
||||
dependencies:
|
||||
@@ -3325,6 +3369,12 @@ packages:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
dev: true
|
||||
|
||||
/yaml@2.4.1:
|
||||
resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==}
|
||||
engines: {node: '>= 14'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/yargs-parser@18.1.3:
|
||||
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
+161
-244
@@ -8,11 +8,12 @@ import { blue, green, red } from "picocolors";
|
||||
import prompts from "prompts";
|
||||
import { InstallAppArgs } from "./create-app";
|
||||
import {
|
||||
FileSourceConfig,
|
||||
TemplateDataSource,
|
||||
TemplateDataSourceType,
|
||||
TemplateFramework,
|
||||
} from "./helpers";
|
||||
import { COMMUNITY_OWNER, COMMUNITY_REPO } from "./helpers/constant";
|
||||
import { EXAMPLE_FILE } from "./helpers/datasources";
|
||||
import { templatesDir } from "./helpers/dir";
|
||||
import { getAvailableLlamapackOptions } from "./helpers/llama-pack";
|
||||
import { getProjectOptions } from "./helpers/repo";
|
||||
@@ -24,8 +25,6 @@ export type QuestionArgs = Omit<
|
||||
InstallAppArgs,
|
||||
"appPath" | "packageManager"
|
||||
> & {
|
||||
files?: string;
|
||||
llamaParse?: boolean;
|
||||
listServerModels?: boolean;
|
||||
};
|
||||
const supportedContextFileTypes = [
|
||||
@@ -40,21 +39,22 @@ const MACOS_FILE_SELECTION_SCRIPT = `
|
||||
osascript -l JavaScript -e '
|
||||
a = Application.currentApplication();
|
||||
a.includeStandardAdditions = true;
|
||||
a.chooseFile({ withPrompt: "Please select a file to process:" }).toString()
|
||||
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 a folder to process:" }).toString()
|
||||
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.FileName
|
||||
$openFileDialog.FileNames
|
||||
}
|
||||
`;
|
||||
const WINDOWS_FOLDER_SELECTION_SCRIPT = `
|
||||
@@ -70,21 +70,17 @@ if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK)
|
||||
const defaults: QuestionArgs = {
|
||||
template: "streaming",
|
||||
framework: "nextjs",
|
||||
engine: "simple",
|
||||
ui: "html",
|
||||
eslint: true,
|
||||
ui: "shadcn",
|
||||
frontend: false,
|
||||
openAiKey: "",
|
||||
llamaCloudKey: "",
|
||||
useLlamaParse: false,
|
||||
model: "gpt-3.5-turbo",
|
||||
embeddingModel: "text-embedding-ada-002",
|
||||
communityProjectConfig: undefined,
|
||||
llamapack: "",
|
||||
postInstallAction: "dependencies",
|
||||
dataSource: {
|
||||
type: "none",
|
||||
config: {},
|
||||
},
|
||||
dataSources: [],
|
||||
tools: [],
|
||||
};
|
||||
|
||||
@@ -122,24 +118,42 @@ const getVectorDbChoices = (framework: TemplateFramework) => {
|
||||
return displayedChoices;
|
||||
};
|
||||
|
||||
const getDataSourceChoices = (framework: TemplateFramework) => {
|
||||
const choices = [
|
||||
{
|
||||
title: "No data, just a simple chat",
|
||||
value: "simple",
|
||||
},
|
||||
{ title: "Use an example PDF", value: "exampleFile" },
|
||||
];
|
||||
if (process.platform === "win32" || process.platform === "darwin") {
|
||||
export const getDataSourceChoices = (
|
||||
framework: TemplateFramework,
|
||||
selectedDataSource: TemplateDataSource[],
|
||||
) => {
|
||||
const choices = [];
|
||||
if (selectedDataSource.length > 0) {
|
||||
choices.push({
|
||||
title: `Use a local file (${supportedContextFileTypes.join(", ")})`,
|
||||
value: "localFile",
|
||||
});
|
||||
choices.push({
|
||||
title: `Use a local folder`,
|
||||
value: "localFolder",
|
||||
title: "No",
|
||||
value: "no",
|
||||
});
|
||||
}
|
||||
if (selectedDataSource === undefined || selectedDataSource.length === 0) {
|
||||
choices.push({
|
||||
title: "No data, just a simple chat",
|
||||
value: "none",
|
||||
});
|
||||
choices.push({
|
||||
title: "Use an example PDF",
|
||||
value: "exampleFile",
|
||||
});
|
||||
}
|
||||
|
||||
choices.push(
|
||||
{
|
||||
title: `Use local files (${supportedContextFileTypes.join(", ")})`,
|
||||
value: "file",
|
||||
},
|
||||
{
|
||||
title:
|
||||
process.platform === "win32"
|
||||
? "Use a local folder"
|
||||
: "Use local folders",
|
||||
value: "folder",
|
||||
},
|
||||
);
|
||||
|
||||
if (framework === "fastapi") {
|
||||
choices.push({
|
||||
title: "Use website content (requires Chrome)",
|
||||
@@ -173,9 +187,16 @@ const selectLocalContextData = async (type: TemplateDataSourceType) => {
|
||||
process.exit(1);
|
||||
}
|
||||
selectedPath = execSync(execScript, execOpts).toString().trim();
|
||||
if (type === "file") {
|
||||
const fileType = path.extname(selectedPath);
|
||||
if (!supportedContextFileTypes.includes(fileType)) {
|
||||
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}`,
|
||||
@@ -184,7 +205,7 @@ const selectLocalContextData = async (type: TemplateDataSourceType) => {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
return selectedPath;
|
||||
return paths;
|
||||
} catch (error) {
|
||||
console.log(
|
||||
red(
|
||||
@@ -309,9 +330,7 @@ export const askQuestions = async (
|
||||
const openAiKeyConfigured =
|
||||
program.openAiKey || process.env["OPENAI_API_KEY"];
|
||||
// If using LlamaParse, require LlamaCloud API key
|
||||
const llamaCloudKeyConfigured = (
|
||||
program.dataSource?.config as FileSourceConfig
|
||||
)?.useLlamaParse
|
||||
const llamaCloudKeyConfigured = program.useLlamaParse
|
||||
? program.llamaCloudKey || process.env["LLAMA_CLOUD_API_KEY"]
|
||||
: true;
|
||||
const hasVectorDb = program.vectorDb && program.vectorDb !== "none";
|
||||
@@ -359,8 +378,7 @@ export const askQuestions = async (
|
||||
name: "template",
|
||||
message: "Which template would you like to use?",
|
||||
choices: [
|
||||
{ title: "Chat without streaming", value: "simple" },
|
||||
{ title: "Chat with streaming", value: "streaming" },
|
||||
{ title: "Chat", value: "streaming" },
|
||||
{
|
||||
title: `Community template from ${styledRepo}`,
|
||||
value: "community",
|
||||
@@ -370,7 +388,7 @@ export const askQuestions = async (
|
||||
value: "llamapack",
|
||||
},
|
||||
],
|
||||
initial: 1,
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
@@ -429,13 +447,10 @@ export const askQuestions = async (
|
||||
program.framework = getPrefOrDefault("framework");
|
||||
} else {
|
||||
const choices = [
|
||||
{ title: "NextJS", value: "nextjs" },
|
||||
{ title: "Express", value: "express" },
|
||||
{ title: "FastAPI (Python)", value: "fastapi" },
|
||||
];
|
||||
if (program.template === "streaming") {
|
||||
// allow NextJS only for streaming template
|
||||
choices.unshift({ title: "NextJS", value: "nextjs" });
|
||||
}
|
||||
|
||||
const { framework } = await prompts(
|
||||
{
|
||||
@@ -452,10 +467,7 @@ export const askQuestions = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
program.template === "streaming" &&
|
||||
(program.framework === "express" || program.framework === "fastapi")
|
||||
) {
|
||||
if (program.framework === "express" || program.framework === "fastapi") {
|
||||
// if a backend-only framework is selected, ask whether we should create a frontend
|
||||
// (only for streaming backends)
|
||||
if (program.frontend === undefined) {
|
||||
@@ -489,25 +501,7 @@ export const askQuestions = async (
|
||||
|
||||
if (program.framework === "nextjs" || program.frontend) {
|
||||
if (!program.ui) {
|
||||
if (ciInfo.isCI) {
|
||||
program.ui = getPrefOrDefault("ui");
|
||||
} else {
|
||||
const { ui } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "ui",
|
||||
message: "Which UI would you like to use?",
|
||||
choices: [
|
||||
{ title: "Just HTML", value: "html" },
|
||||
{ title: "Shadcn", value: "shadcn" },
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.ui = ui;
|
||||
preferences.ui = ui;
|
||||
}
|
||||
program.ui = getPrefOrDefault("ui");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,130 +601,110 @@ export const askQuestions = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (program.files) {
|
||||
// If user specified files option, then the program should use context engine
|
||||
program.engine == "context";
|
||||
if (!fs.existsSync(program.files)) {
|
||||
console.log("File or folder not found");
|
||||
process.exit(1);
|
||||
} else {
|
||||
program.dataSource = {
|
||||
type: fs.lstatSync(program.files).isDirectory() ? "folder" : "file",
|
||||
config: {
|
||||
path: program.files,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.engine) {
|
||||
if (!program.dataSources) {
|
||||
if (ciInfo.isCI) {
|
||||
program.engine = getPrefOrDefault("engine");
|
||||
program.dataSources = getPrefOrDefault("dataSources");
|
||||
} else {
|
||||
const { dataSource } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "dataSource",
|
||||
message: "Which data source would you like to use?",
|
||||
choices: getDataSourceChoices(program.framework),
|
||||
initial: 1,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
// Initialize with default config
|
||||
program.dataSource = getPrefOrDefault("dataSource");
|
||||
if (program.dataSource) {
|
||||
switch (dataSource) {
|
||||
case "simple":
|
||||
program.engine = "simple";
|
||||
program.dataSource = { type: "none", config: {} };
|
||||
break;
|
||||
case "exampleFile":
|
||||
program.engine = "context";
|
||||
// Treat example as a folder data source with no config
|
||||
program.dataSource = { type: "folder", config: {} };
|
||||
break;
|
||||
case "localFile":
|
||||
program.engine = "context";
|
||||
program.dataSource = {
|
||||
type: "file",
|
||||
config: {
|
||||
path: await selectLocalContextData("file"),
|
||||
},
|
||||
};
|
||||
break;
|
||||
case "localFolder":
|
||||
program.engine = "context";
|
||||
program.dataSource = {
|
||||
type: "folder",
|
||||
config: {
|
||||
path: await selectLocalContextData("folder"),
|
||||
},
|
||||
};
|
||||
break;
|
||||
case "web":
|
||||
program.engine = "context";
|
||||
program.dataSource.type = "web";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!program.dataSource) {
|
||||
// Handle a case when engine is specified but dataSource is not
|
||||
if (program.engine === "context") {
|
||||
program.dataSource = {
|
||||
type: "folder",
|
||||
config: {},
|
||||
};
|
||||
} else if (program.engine === "simple") {
|
||||
program.dataSource = {
|
||||
type: "none",
|
||||
config: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
program.dataSource?.type === "file" ||
|
||||
program.dataSource?.type === "folder"
|
||||
) {
|
||||
if (ciInfo.isCI) {
|
||||
program.llamaCloudKey = getPrefOrDefault("llamaCloudKey");
|
||||
} else {
|
||||
const dataSourceConfig = program.dataSource.config as FileSourceConfig;
|
||||
dataSourceConfig.useLlamaParse = program.llamaParse;
|
||||
|
||||
// Is pdf file selected as data source or is it a folder data source
|
||||
const askingLlamaParse =
|
||||
dataSourceConfig.useLlamaParse === undefined &&
|
||||
(program.dataSource.type === "folder"
|
||||
? true
|
||||
: dataSourceConfig.path &&
|
||||
path.extname(dataSourceConfig.path) === ".pdf");
|
||||
|
||||
// Ask if user wants to use LlamaParse
|
||||
if (askingLlamaParse) {
|
||||
const { useLlamaParse } = await prompts(
|
||||
program.dataSources = [];
|
||||
// continue asking user for data sources if none are initially provided
|
||||
while (true) {
|
||||
const firstQuestion = program.dataSources.length === 0;
|
||||
const { selectedSource } = await prompts(
|
||||
{
|
||||
type: "toggle",
|
||||
name: "useLlamaParse",
|
||||
message:
|
||||
"Would you like to use LlamaParse (improved parser for RAG - requires API key)?",
|
||||
initial: true,
|
||||
active: "yes",
|
||||
inactive: "no",
|
||||
type: "select",
|
||||
name: "selectedSource",
|
||||
message: firstQuestion
|
||||
? "Which data source would you like to use?"
|
||||
: "Would you like to add another data source?",
|
||||
choices: getDataSourceChoices(
|
||||
program.framework,
|
||||
program.dataSources,
|
||||
),
|
||||
initial: firstQuestion ? 1 : 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
dataSourceConfig.useLlamaParse = useLlamaParse;
|
||||
program.dataSource.config = dataSourceConfig;
|
||||
|
||||
if (selectedSource === "no" || selectedSource === "none") {
|
||||
// user doesn't want another data source or any data source
|
||||
break;
|
||||
}
|
||||
if (selectedSource === "exampleFile") {
|
||||
program.dataSources.push(EXAMPLE_FILE);
|
||||
} else if (selectedSource === "file" || selectedSource === "folder") {
|
||||
// Select local data source
|
||||
const selectedPaths = await selectLocalContextData(selectedSource);
|
||||
for (const p of selectedPaths) {
|
||||
program.dataSources.push({
|
||||
type: "file",
|
||||
config: {
|
||||
path: p,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (selectedSource === "web") {
|
||||
// Selected web data source
|
||||
const { baseUrl } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
name: "baseUrl",
|
||||
message: "Please provide base URL of the website: ",
|
||||
initial: "https://www.llamaindex.ai",
|
||||
validate: (value: string) => {
|
||||
if (!value.includes("://")) {
|
||||
value = `https://${value}`;
|
||||
}
|
||||
const urlObj = new URL(value);
|
||||
if (
|
||||
urlObj.protocol !== "https:" &&
|
||||
urlObj.protocol !== "http:"
|
||||
) {
|
||||
return `URL=${value} has invalid protocol, only allow http or https`;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
|
||||
program.dataSources.push({
|
||||
type: "web",
|
||||
config: {
|
||||
baseUrl,
|
||||
prefix: baseUrl,
|
||||
depth: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Asking for LlamaParse if user selected file or folder data source
|
||||
if (
|
||||
program.dataSources.some((ds) => ds.type === "file") &&
|
||||
program.useLlamaParse === undefined
|
||||
) {
|
||||
if (ciInfo.isCI) {
|
||||
program.useLlamaParse = getPrefOrDefault("useLlamaParse");
|
||||
program.llamaCloudKey = getPrefOrDefault("llamaCloudKey");
|
||||
} else {
|
||||
const { useLlamaParse } = await prompts(
|
||||
{
|
||||
type: "toggle",
|
||||
name: "useLlamaParse",
|
||||
message:
|
||||
"Would you like to use LlamaParse (improved parser for RAG - requires API key)?",
|
||||
initial: false,
|
||||
active: "yes",
|
||||
inactive: "no",
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.useLlamaParse = useLlamaParse;
|
||||
|
||||
// Ask for LlamaCloud API key
|
||||
if (
|
||||
dataSourceConfig.useLlamaParse &&
|
||||
program.llamaCloudKey === undefined
|
||||
) {
|
||||
if (useLlamaParse && program.llamaCloudKey === undefined) {
|
||||
const { llamaCloudKey } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
@@ -745,39 +719,7 @@ export const askQuestions = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (program.dataSource?.type === "web" && program.framework === "fastapi") {
|
||||
let { baseUrl } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
name: "baseUrl",
|
||||
message: "Please provide base URL of the website:",
|
||||
initial: "https://www.llamaindex.ai",
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
try {
|
||||
if (!baseUrl.includes("://")) {
|
||||
baseUrl = `https://${baseUrl}`;
|
||||
}
|
||||
const checkUrl = new URL(baseUrl);
|
||||
if (checkUrl.protocol !== "https:" && checkUrl.protocol !== "http:") {
|
||||
throw new Error("Invalid protocol");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
red(
|
||||
"Invalid URL provided! Please provide a valid URL (e.g. https://www.llamaindex.ai)",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
program.dataSource.config = {
|
||||
baseUrl: baseUrl,
|
||||
depth: 1,
|
||||
};
|
||||
}
|
||||
|
||||
if (program.engine !== "simple" && !program.vectorDb) {
|
||||
if (program.dataSources.length > 0 && !program.vectorDb) {
|
||||
if (ciInfo.isCI) {
|
||||
program.vectorDb = getPrefOrDefault("vectorDb");
|
||||
} else {
|
||||
@@ -796,15 +738,15 @@ export const askQuestions = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!program.tools &&
|
||||
program.framework === "fastapi" &&
|
||||
program.engine === "context"
|
||||
) {
|
||||
// TODO: allow tools also without datasources
|
||||
if (!program.tools && program.dataSources.length > 0) {
|
||||
if (ciInfo.isCI) {
|
||||
program.tools = getPrefOrDefault("tools");
|
||||
} else {
|
||||
const toolChoices = supportedTools.map((tool) => ({
|
||||
const options = supportedTools.filter((t) =>
|
||||
t.supportedFrameworks?.includes(program.framework),
|
||||
);
|
||||
const toolChoices = options.map((tool) => ({
|
||||
title: tool.display,
|
||||
value: tool.name,
|
||||
}));
|
||||
@@ -823,30 +765,5 @@ export const askQuestions = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (program.framework !== "fastapi" && program.eslint === undefined) {
|
||||
if (ciInfo.isCI) {
|
||||
program.eslint = getPrefOrDefault("eslint");
|
||||
} else {
|
||||
const styledEslint = blue("ESLint");
|
||||
const { eslint } = await prompts({
|
||||
onState: onPromptState,
|
||||
type: "toggle",
|
||||
name: "eslint",
|
||||
message: `Would you like to use ${styledEslint}?`,
|
||||
initial: getPrefOrDefault("eslint"),
|
||||
active: "Yes",
|
||||
inactive: "No",
|
||||
});
|
||||
program.eslint = Boolean(eslint);
|
||||
preferences.eslint = Boolean(eslint);
|
||||
}
|
||||
}
|
||||
|
||||
await askPostInstallAction();
|
||||
|
||||
// TODO: consider using zod to validate the input (doesn't work like this as not every option is required)
|
||||
// templateUISchema.parse(program.ui);
|
||||
// templateEngineSchema.parse(program.engine);
|
||||
// templateFrameworkSchema.parse(program.framework);
|
||||
// templateTypeSchema.parse(program.template);``
|
||||
};
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# build dist/index.js file
|
||||
pnpm run build:ncc
|
||||
|
||||
# add shebang to the top of dist/index.js
|
||||
# XXX: Windows needs a space after `node` to work correctly
|
||||
# Note: ncc can handle shebang but it didn't work with Windows in our tests
|
||||
echo '#!/usr/bin/env node ' | cat - dist/index.js >temp && mv temp dist/index.js
|
||||
|
||||
# make dist/index.js executable
|
||||
chmod +x dist/index.js
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
pnpm pack && npm install -g $(pwd)/$(ls ./*.tgz | head -1)
|
||||
@@ -0,0 +1,26 @@
|
||||
FROM python:3.11 as build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV PYTHONPATH=/app
|
||||
|
||||
# Install Poetry
|
||||
RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \
|
||||
cd /usr/local/bin && \
|
||||
ln -s /opt/poetry/bin/poetry && \
|
||||
poetry config virtualenvs.create false
|
||||
|
||||
# Install Chromium for web loader
|
||||
# Can disable this if you don't use the web loader to reduce the image size
|
||||
RUN apt update && apt install -y chromium chromium-driver
|
||||
|
||||
# Install dependencies
|
||||
COPY ./pyproject.toml ./poetry.lock* /app/
|
||||
RUN poetry install --no-root --no-cache --only main
|
||||
|
||||
# ====================================
|
||||
FROM build as release
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM node:20-alpine as build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package.json package-lock.* ./
|
||||
RUN npm install
|
||||
|
||||
# Build the application
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# ====================================
|
||||
FROM build as release
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
@@ -1,4 +1,4 @@
|
||||
import json
|
||||
import yaml
|
||||
import importlib
|
||||
|
||||
from llama_index.core.tools.tool_spec.base import BaseToolSpec
|
||||
@@ -26,8 +26,8 @@ class ToolFactory:
|
||||
@staticmethod
|
||||
def from_env() -> list[FunctionTool]:
|
||||
tools = []
|
||||
with open("tools_config.json", "r") as f:
|
||||
tool_configs = json.load(f)
|
||||
with open("config/tools.yaml", "r") as f:
|
||||
tool_configs = yaml.safe_load(f)
|
||||
for name, config in tool_configs.items():
|
||||
tools += ToolFactory.create_tool(name, **config)
|
||||
return tools
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import config from "@/config/tools.json";
|
||||
import { OpenAI, OpenAIAgent, QueryEngineTool, ToolFactory } from "llamaindex";
|
||||
import { STORAGE_CACHE_DIR } from "./constants.mjs";
|
||||
import { getDataSource } from "./index";
|
||||
|
||||
export async function createChatEngine(llm: OpenAI) {
|
||||
const index = await getDataSource(llm);
|
||||
const queryEngine = index.asQueryEngine();
|
||||
const queryEngineTool = new QueryEngineTool({
|
||||
queryEngine: queryEngine,
|
||||
metadata: {
|
||||
name: "data_query_engine",
|
||||
description: `A query engine for documents in storage folder: ${STORAGE_CACHE_DIR}`,
|
||||
},
|
||||
});
|
||||
|
||||
const externalTools = await ToolFactory.createTools(config);
|
||||
|
||||
const agent = new OpenAIAgent({
|
||||
tools: [queryEngineTool, ...externalTools],
|
||||
verbose: true,
|
||||
llm,
|
||||
});
|
||||
|
||||
return agent;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ContextChatEngine, LLM } from "llamaindex";
|
||||
import { getDataSource } from "./index";
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever();
|
||||
retriever.similarityTopK = 3;
|
||||
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
retriever,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import os
|
||||
import yaml
|
||||
import importlib
|
||||
import logging
|
||||
from typing import Dict
|
||||
from app.engine.loaders.file import FileLoaderConfig, get_file_documents
|
||||
from app.engine.loaders.web import WebLoaderConfig, get_web_documents
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_configs():
|
||||
with open("config/loaders.yaml") as f:
|
||||
configs = yaml.safe_load(f)
|
||||
return configs
|
||||
|
||||
|
||||
def get_documents():
|
||||
documents = []
|
||||
config = load_configs()
|
||||
for loader_type, loader_config in config.items():
|
||||
logger.info(
|
||||
f"Loading documents from loader: {loader_type}, config: {loader_config}"
|
||||
)
|
||||
if loader_type == "file":
|
||||
document = get_file_documents(FileLoaderConfig(**loader_config))
|
||||
documents.extend(document)
|
||||
elif loader_type == "web":
|
||||
document = get_web_documents(WebLoaderConfig(**loader_config))
|
||||
documents.extend(document)
|
||||
|
||||
return documents
|
||||
@@ -0,0 +1,37 @@
|
||||
import os
|
||||
from llama_parse import LlamaParse
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
|
||||
class FileLoaderConfig(BaseModel):
|
||||
data_dir: str = "data"
|
||||
use_llama_parse: bool = False
|
||||
|
||||
@validator("data_dir")
|
||||
def data_dir_must_exist(cls, v):
|
||||
if not os.path.isdir(v):
|
||||
raise ValueError(f"Directory '{v}' does not exist")
|
||||
return v
|
||||
|
||||
|
||||
def llama_parse_parser():
|
||||
if os.getenv("LLAMA_CLOUD_API_KEY") is None:
|
||||
raise ValueError(
|
||||
"LLAMA_CLOUD_API_KEY environment variable is not set. "
|
||||
"Please set it in .env file or in your shell environment then run again!"
|
||||
)
|
||||
parser = LlamaParse(result_type="markdown", verbose=True, language="en")
|
||||
return parser
|
||||
|
||||
|
||||
def get_file_documents(config: FileLoaderConfig):
|
||||
from llama_index.core.readers import SimpleDirectoryReader
|
||||
|
||||
reader = SimpleDirectoryReader(
|
||||
config.data_dir,
|
||||
recursive=True,
|
||||
)
|
||||
if config.use_llama_parse:
|
||||
parser = llama_parse_parser()
|
||||
reader.file_extractor = {".pdf": parser}
|
||||
return reader.load_data()
|
||||
@@ -1,7 +0,0 @@
|
||||
from llama_index.core.readers import SimpleDirectoryReader
|
||||
|
||||
DATA_DIR = "data" # directory containing the documents
|
||||
|
||||
|
||||
def get_documents():
|
||||
return SimpleDirectoryReader(DATA_DIR).load_data()
|
||||
@@ -1,17 +0,0 @@
|
||||
import os
|
||||
from llama_parse import LlamaParse
|
||||
from llama_index.core import SimpleDirectoryReader
|
||||
|
||||
DATA_DIR = "data" # directory containing the documents
|
||||
|
||||
|
||||
def get_documents():
|
||||
if os.getenv("LLAMA_CLOUD_API_KEY") is None:
|
||||
raise ValueError(
|
||||
"LLAMA_CLOUD_API_KEY environment variable is not set. "
|
||||
"Please set it in .env file or in your shell environment then run again!"
|
||||
)
|
||||
parser = LlamaParse(result_type="markdown", verbose=True, language="en")
|
||||
|
||||
reader = SimpleDirectoryReader(DATA_DIR, file_extractor={".pdf": parser})
|
||||
return reader.load_data()
|
||||
@@ -0,0 +1,36 @@
|
||||
import os
|
||||
import json
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class CrawlUrl(BaseModel):
|
||||
base_url: str
|
||||
prefix: str
|
||||
max_depth: int = Field(default=1, ge=0)
|
||||
|
||||
|
||||
class WebLoaderConfig(BaseModel):
|
||||
driver_arguments: list[str] = Field(default=None)
|
||||
urls: list[CrawlUrl]
|
||||
|
||||
|
||||
def get_web_documents(config: WebLoaderConfig):
|
||||
from llama_index.readers.web import WholeSiteReader
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
|
||||
options = Options()
|
||||
driver_arguments = config.driver_arguments or []
|
||||
for arg in driver_arguments:
|
||||
options.add_argument(arg)
|
||||
|
||||
docs = []
|
||||
for url in config.urls:
|
||||
scraper = WholeSiteReader(
|
||||
prefix=url.prefix,
|
||||
max_depth=url.max_depth,
|
||||
driver=webdriver.Chrome(options=options),
|
||||
)
|
||||
docs.extend(scraper.load_data(url.base_url))
|
||||
|
||||
return docs
|
||||
@@ -1,13 +0,0 @@
|
||||
import os
|
||||
from llama_index.readers.web import WholeSiteReader
|
||||
|
||||
|
||||
def get_documents():
|
||||
# Initialize the scraper with a prefix URL and maximum depth
|
||||
scraper = WholeSiteReader(
|
||||
prefix=os.environ.get("URL_PREFIX"), max_depth=int(os.environ.get("MAX_DEPTH"))
|
||||
)
|
||||
# Start scraping from a base URL
|
||||
documents = scraper.load_data(base_url=os.environ.get("BASE_URL"))
|
||||
|
||||
return documents
|
||||
@@ -7,7 +7,7 @@ export default function ChatItem(message: Message) {
|
||||
return (
|
||||
<div className="flex items-start gap-4 pt-5">
|
||||
<ChatAvatar {...message} />
|
||||
<p className="break-words">{message.content}</p>
|
||||
<p className="break-words whitespace-pre-wrap">{message.content}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ from llama_index.core.storage import StorageContext
|
||||
from llama_index.core.indices import VectorStoreIndex
|
||||
from llama_index.vector_stores.milvus import MilvusVectorStore
|
||||
from app.settings import init_settings
|
||||
from app.engine.loader import get_documents
|
||||
from app.engine.loaders import get_documents
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
|
||||
@@ -8,7 +8,7 @@ from llama_index.core.storage import StorageContext
|
||||
from llama_index.core.indices import VectorStoreIndex
|
||||
from llama_index.vector_stores.mongodb import MongoDBAtlasVectorSearch
|
||||
from app.settings import init_settings
|
||||
from app.engine.loader import get_documents
|
||||
from app.engine.loaders import get_documents
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
|
||||
@@ -7,7 +7,7 @@ from llama_index.core.indices import (
|
||||
VectorStoreIndex,
|
||||
)
|
||||
from app.engine.constants import STORAGE_DIR
|
||||
from app.engine.loader import get_documents
|
||||
from app.engine.loaders import get_documents
|
||||
from app.settings import init_settings
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import logging
|
||||
from llama_index.core.indices import VectorStoreIndex
|
||||
from llama_index.core.storage import StorageContext
|
||||
|
||||
from app.engine.loader import get_documents
|
||||
from app.engine.loaders import get_documents
|
||||
from app.settings import init_settings
|
||||
from app.engine.utils import init_pg_vector_store_from_env
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from llama_index.core.storage import StorageContext
|
||||
from llama_index.core.indices import VectorStoreIndex
|
||||
from llama_index.vector_stores.pinecone import PineconeVectorStore
|
||||
from app.settings import init_settings
|
||||
from app.engine.loader import get_documents
|
||||
from app.engine.loaders import get_documents
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
ContextChatEngine,
|
||||
LLM,
|
||||
serviceContextFromDefaults,
|
||||
SimpleDocumentStore,
|
||||
@@ -8,7 +7,7 @@ import {
|
||||
} from "llamaindex";
|
||||
import { CHUNK_OVERLAP, CHUNK_SIZE, STORAGE_CACHE_DIR } from "./constants.mjs";
|
||||
|
||||
async function getDataSource(llm: LLM) {
|
||||
export async function getDataSource(llm: LLM) {
|
||||
const serviceContext = serviceContextFromDefaults({
|
||||
llm,
|
||||
chunkSize: CHUNK_SIZE,
|
||||
@@ -31,14 +30,3 @@ async function getDataSource(llm: LLM) {
|
||||
serviceContext,
|
||||
});
|
||||
}
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever();
|
||||
retriever.similarityTopK = 3;
|
||||
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
retriever,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
This is a [LlamaIndex](https://www.llamaindex.ai/) project using [Express](https://expressjs.com/) bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, install the dependencies:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
Second, run the development server:
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then call the express API endpoint `/api/chat` to see the result:
|
||||
|
||||
```
|
||||
curl --location 'localhost:8000/api/chat' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{ "messages": [{ "role": "user", "content": "Hello" }] }'
|
||||
```
|
||||
|
||||
You can start editing the API by modifying `src/controllers/chat.controller.ts`. The endpoint auto-updates as you save the file.
|
||||
|
||||
## Production
|
||||
|
||||
First, build the project:
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can then run the production server:
|
||||
|
||||
```
|
||||
NODE_ENV=production npm run start
|
||||
```
|
||||
|
||||
> Note that the `NODE_ENV` environment variable is set to `production`. This disables CORS for all origins.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about LlamaIndex, take a look at the following resources:
|
||||
|
||||
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex (Python features).
|
||||
- [LlamaIndexTS Documentation](https://ts.llamaindex.ai) - learn about LlamaIndex (Typescript features).
|
||||
|
||||
You can check out [the LlamaIndexTS GitHub repository](https://github.com/run-llama/LlamaIndexTS) - your feedback and contributions are welcome!
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "eslint:recommended"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# local env files
|
||||
.env
|
||||
node_modules/
|
||||
@@ -1,44 +0,0 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import cors from "cors";
|
||||
import "dotenv/config";
|
||||
import express, { Express, Request, Response } from "express";
|
||||
import { initObservability } from "./src/observability";
|
||||
import chatRouter from "./src/routes/chat.route";
|
||||
|
||||
const app: Express = express();
|
||||
const port = parseInt(process.env.PORT || "8000");
|
||||
|
||||
const env = process.env["NODE_ENV"];
|
||||
const isDevelopment = !env || env === "development";
|
||||
const prodCorsOrigin = process.env["PROD_CORS_ORIGIN"];
|
||||
|
||||
initObservability();
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
if (isDevelopment) {
|
||||
console.warn("Running in development mode - allowing CORS for all origins");
|
||||
app.use(cors());
|
||||
} else if (prodCorsOrigin) {
|
||||
console.log(
|
||||
`Running in production mode - allowing CORS for domain: ${prodCorsOrigin}`,
|
||||
);
|
||||
const corsOptions = {
|
||||
origin: prodCorsOrigin, // Restrict to production domain
|
||||
};
|
||||
app.use(cors(corsOptions));
|
||||
} else {
|
||||
console.warn("Production CORS origin not set, defaulting to no CORS.");
|
||||
}
|
||||
|
||||
app.use(express.text());
|
||||
|
||||
app.get("/", (req: Request, res: Response) => {
|
||||
res.send("LlamaIndex Express Server");
|
||||
});
|
||||
|
||||
app.use("/api/chat", chatRouter);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`⚡️[server]: Server is running at http://localhost:${port}`);
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "llama-index-express",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsup index.ts --format esm --dts",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "concurrently \"tsup index.ts --format esm --dts --watch\" \"nodemon -q dist/index.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"llamaindex": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.9.5",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8.54.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"tsup": "^7.3.0",
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export const initObservability = () => {};
|
||||
@@ -1,8 +0,0 @@
|
||||
import express from "express";
|
||||
import { chat } from "../controllers/chat.controller";
|
||||
|
||||
const llmRouter = express.Router();
|
||||
|
||||
llmRouter.route("/").post(chat);
|
||||
|
||||
export default llmRouter;
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
This is a [LlamaIndex](https://www.llamaindex.ai/) project using [FastAPI](https://fastapi.tiangolo.com/) bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, setup the environment with poetry:
|
||||
|
||||
> **_Note:_** This step is not needed if you are using the dev-container.
|
||||
|
||||
```
|
||||
poetry install
|
||||
poetry shell
|
||||
```
|
||||
|
||||
By default, we use the OpenAI LLM (though you can customize, see app/api/routers/chat.py). As a result you need to specify an `OPENAI_API_KEY` in an .env file in this directory.
|
||||
|
||||
Example `backend/.env` file:
|
||||
|
||||
```
|
||||
OPENAI_API_KEY=<openai_api_key>
|
||||
```
|
||||
|
||||
Second, generate the embeddings of the documents in the `./data` directory (if this folder exists - otherwise, skip this step):
|
||||
|
||||
```
|
||||
python app/engine/generate.py
|
||||
```
|
||||
|
||||
Third, run the development server:
|
||||
|
||||
```
|
||||
python main.py
|
||||
```
|
||||
|
||||
Then call the API endpoint `/api/chat` to see the result:
|
||||
|
||||
```
|
||||
curl --location 'localhost:8000/api/chat' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{ "messages": [{ "role": "user", "content": "Hello" }] }'
|
||||
```
|
||||
|
||||
You can start editing the API by modifying `app/api/routers/chat.py`. The endpoint auto-updates as you save the file.
|
||||
|
||||
Open [http://localhost:8000/docs](http://localhost:8000/docs) with your browser to see the Swagger UI of the API.
|
||||
|
||||
The API allows CORS for all origins to simplify development. You can change this behavior by setting the `ENVIRONMENT` environment variable to `prod`:
|
||||
|
||||
```
|
||||
ENVIRONMENT=prod python main.py
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about LlamaIndex, take a look at the following resources:
|
||||
|
||||
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex.
|
||||
|
||||
You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome!
|
||||
@@ -1,54 +0,0 @@
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from llama_index.core.chat_engine.types import BaseChatEngine
|
||||
from llama_index.core.llms import ChatMessage, MessageRole
|
||||
from app.engine import get_chat_engine
|
||||
|
||||
chat_router = r = APIRouter()
|
||||
|
||||
|
||||
class _Message(BaseModel):
|
||||
role: MessageRole
|
||||
content: str
|
||||
|
||||
|
||||
class _ChatData(BaseModel):
|
||||
messages: List[_Message]
|
||||
|
||||
|
||||
class _Result(BaseModel):
|
||||
result: _Message
|
||||
|
||||
|
||||
@r.post("")
|
||||
async def chat(
|
||||
data: _ChatData,
|
||||
chat_engine: BaseChatEngine = Depends(get_chat_engine),
|
||||
) -> _Result:
|
||||
# check preconditions and get last message
|
||||
if len(data.messages) == 0:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No messages provided",
|
||||
)
|
||||
lastMessage = data.messages.pop()
|
||||
if lastMessage.role != MessageRole.USER:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Last message must be from user",
|
||||
)
|
||||
# convert messages coming from the request to type ChatMessage
|
||||
messages = [
|
||||
ChatMessage(
|
||||
role=m.role,
|
||||
content=m.content,
|
||||
)
|
||||
for m in data.messages
|
||||
]
|
||||
|
||||
# query chat engine
|
||||
response = await chat_engine.achat(lastMessage.content, messages)
|
||||
return _Result(
|
||||
result=_Message(role=MessageRole.ASSISTANT, content=response.response)
|
||||
)
|
||||
@@ -1,5 +0,0 @@
|
||||
from llama_index.core.chat_engine import SimpleChatEngine
|
||||
|
||||
|
||||
def get_chat_engine():
|
||||
return SimpleChatEngine.from_defaults()
|
||||
@@ -1,41 +0,0 @@
|
||||
import os
|
||||
from typing import Dict
|
||||
from llama_index.core.settings import Settings
|
||||
from llama_index.llms.openai import OpenAI
|
||||
from llama_index.embeddings.openai import OpenAIEmbedding
|
||||
|
||||
|
||||
def llm_config_from_env() -> Dict:
|
||||
from llama_index.core.constants import DEFAULT_TEMPERATURE
|
||||
|
||||
model = os.getenv("MODEL")
|
||||
temperature = os.getenv("LLM_TEMPERATURE", DEFAULT_TEMPERATURE)
|
||||
max_tokens = os.getenv("LLM_MAX_TOKENS")
|
||||
|
||||
config = {
|
||||
"model": model,
|
||||
"temperature": float(temperature),
|
||||
"max_tokens": int(max_tokens) if max_tokens is not None else None,
|
||||
}
|
||||
return config
|
||||
|
||||
|
||||
def embedding_config_from_env() -> Dict:
|
||||
model = os.getenv("EMBEDDING_MODEL")
|
||||
dimension = os.getenv("EMBEDDING_DIM")
|
||||
|
||||
config = {
|
||||
"model": model,
|
||||
"dimension": int(dimension) if dimension is not None else None,
|
||||
}
|
||||
return config
|
||||
|
||||
|
||||
def init_settings():
|
||||
llm_configs = llm_config_from_env()
|
||||
embedding_configs = embedding_config_from_env()
|
||||
|
||||
Settings.llm = OpenAI(**llm_configs)
|
||||
Settings.embed_model = OpenAIEmbedding(**embedding_configs)
|
||||
Settings.chunk_size = int(os.getenv("CHUNK_SIZE", "1024"))
|
||||
Settings.chunk_overlap = int(os.getenv("CHUNK_OVERLAP", "20"))
|
||||
@@ -1,3 +0,0 @@
|
||||
__pycache__
|
||||
storage
|
||||
.env
|
||||
@@ -1,39 +0,0 @@
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
import logging
|
||||
import os
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.api.routers.chat import chat_router
|
||||
from app.settings import init_settings
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
init_settings()
|
||||
|
||||
environment = os.getenv("ENVIRONMENT", "dev") # Default to 'development' if not set
|
||||
|
||||
|
||||
if environment == "dev":
|
||||
logger = logging.getLogger("uvicorn")
|
||||
logger.warning("Running in development mode - allowing CORS for all origins")
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(chat_router, prefix="/api/chat")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app_host = os.getenv("APP_HOST", "0.0.0.0")
|
||||
app_port = int(os.getenv("APP_PORT", "8000"))
|
||||
reload = True if environment == "dev" else False
|
||||
|
||||
uvicorn.run(app="main:app", host=app_host, port=app_port, reload=reload)
|
||||
@@ -1,17 +0,0 @@
|
||||
[tool.poetry]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Marcus Schiesser <mail@marcusschiesser.de>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11,<3.12"
|
||||
fastapi = "^0.109.1"
|
||||
uvicorn = { extras = ["standard"], version = "^0.23.2" }
|
||||
python-dotenv = "^1.0.0"
|
||||
llama-index = "^0.10.7"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@@ -8,21 +8,41 @@ First, install the dependencies:
|
||||
npm install
|
||||
```
|
||||
|
||||
Second, run the development server:
|
||||
Second, generate the embeddings of the documents in the `./data` directory (if this folder exists - otherwise, skip this step):
|
||||
|
||||
```
|
||||
npm run generate
|
||||
```
|
||||
|
||||
Third, run the development server:
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then call the express API endpoint `/api/chat` to see the result:
|
||||
The example provides two different API endpoints:
|
||||
|
||||
1. `/api/chat` - a streaming chat endpoint (found in `src/controllers/chat.controller.ts`)
|
||||
2. `/api/chat/request` - a non-streaming chat endpoint (found in `src/controllers/chat-request.controller.ts`)
|
||||
|
||||
You can test the streaming endpoint with the following curl request:
|
||||
|
||||
```
|
||||
curl --location 'localhost:8000/api/chat' \
|
||||
--header 'Content-Type: text/plain' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{ "messages": [{ "role": "user", "content": "Hello" }] }'
|
||||
```
|
||||
|
||||
You can start editing the API by modifying `src/controllers/chat.controller.ts`. The endpoint auto-updates as you save the file.
|
||||
And for the non-streaming endpoint run:
|
||||
|
||||
```
|
||||
curl --location 'localhost:8000/api/chat/request' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{ "messages": [{ "role": "user", "content": "Hello" }] }'
|
||||
```
|
||||
|
||||
You can start editing the API by modifying `src/controllers/chat.controller.ts` or `src/controllers/chat-request.controller.ts`. The endpoint auto-updates as you save the file.
|
||||
You can delete the endpoint that you're not using.
|
||||
|
||||
## Production
|
||||
|
||||
@@ -40,6 +60,39 @@ NODE_ENV=production npm run start
|
||||
|
||||
> Note that the `NODE_ENV` environment variable is set to `production`. This disables CORS for all origins.
|
||||
|
||||
## Using Docker
|
||||
|
||||
1. Build an image for the Express API:
|
||||
|
||||
```
|
||||
docker build -t <your_backend_image_name> .
|
||||
```
|
||||
|
||||
2. Generate embeddings:
|
||||
|
||||
Parse the data and generate the vector embeddings if the `./data` folder exists - otherwise, skip this step:
|
||||
|
||||
```
|
||||
docker run --rm \
|
||||
-v $(pwd)/.env:/app/.env \ # Use ENV variables and configuration from your file-system
|
||||
-v $(pwd)/config:/app/config \
|
||||
-v $(pwd)/data:/app/data \
|
||||
-v $(pwd)/cache:/app/cache \ # Use your file system to store the vector database
|
||||
<your_backend_image_name>
|
||||
npm run generate
|
||||
```
|
||||
|
||||
3. Start the API:
|
||||
|
||||
```
|
||||
docker run \
|
||||
-v $(pwd)/.env:/app/.env \ # Use ENV variables and configuration from your file-system
|
||||
-v $(pwd)/config:/app/config \
|
||||
-v $(pwd)/cache:/app/cache \ # Use your file system to store the vector database
|
||||
-p 8000:8000 \
|
||||
<your_backend_image_name>
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about LlamaIndex, take a look at the following resources:
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
{
|
||||
"extends": "eslint:recommended"
|
||||
"extends": ["eslint:recommended", "prettier"],
|
||||
"rules": {
|
||||
"max-params": ["error", 4],
|
||||
"prefer-const": "error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "llama-index-express-streaming",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"main": "dist/index.mjs",
|
||||
"scripts": {
|
||||
"format": "prettier --ignore-unknown --cache --check .",
|
||||
"format:write": "prettier --ignore-unknown --write .",
|
||||
"build": "tsup index.ts --format esm --dts",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "concurrently \"tsup index.ts --format esm --dts --watch\" \"nodemon -q dist/index.js\""
|
||||
"start": "node dist/index.mjs",
|
||||
"dev": "concurrently \"tsup index.ts --format esm --dts --watch\" \"nodemon -q dist/index.mjs\""
|
||||
},
|
||||
"dependencies": {
|
||||
"ai": "^2.2.25",
|
||||
@@ -23,6 +24,9 @@
|
||||
"eslint": "^8.54.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"tsup": "^8.0.1",
|
||||
"typescript": "^5.3.2"
|
||||
"typescript": "^5.3.2",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"eslint-config-prettier": "^8.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
plugins: ["prettier-plugin-organize-imports"],
|
||||
};
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import { Request, Response } from "express";
|
||||
import { ChatMessage, MessageContent, OpenAI } from "llamaindex";
|
||||
import { createChatEngine } from "./engine";
|
||||
import { createChatEngine } from "./engine/chat";
|
||||
|
||||
const convertMessageContent = (
|
||||
textMessage: string,
|
||||
@@ -21,7 +21,7 @@ const convertMessageContent = (
|
||||
];
|
||||
};
|
||||
|
||||
export const chat = async (req: Request, res: Response) => {
|
||||
export const chatRequest = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { messages, data }: { messages: ChatMessage[]; data: any } = req.body;
|
||||
const userMessage = messages.pop();
|
||||
@@ -48,7 +48,7 @@ export const chat = async (req: Request, res: Response) => {
|
||||
// Calling LlamaIndex's ChatEngine to get a response
|
||||
const response = await chatEngine.chat({
|
||||
message: userMessageContent,
|
||||
messages,
|
||||
chatHistory: messages,
|
||||
});
|
||||
const result: ChatMessage = {
|
||||
role: "assistant",
|
||||
@@ -1,7 +1,7 @@
|
||||
import { streamToResponse } from "ai";
|
||||
import { Request, Response } from "express";
|
||||
import { ChatMessage, MessageContent, OpenAI } from "llamaindex";
|
||||
import { createChatEngine } from "./engine";
|
||||
import { createChatEngine } from "./engine/chat";
|
||||
import { LlamaIndexStream } from "./llamaindex-stream";
|
||||
|
||||
const convertMessageContent = (
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
trimStartOfStreamHelper,
|
||||
type AIStreamCallbacksAndOptions,
|
||||
} from "ai";
|
||||
import { Response } from "llamaindex";
|
||||
import { Response, StreamingAgentChatResponse } from "llamaindex";
|
||||
|
||||
type ParserOptions = {
|
||||
image_url?: string;
|
||||
@@ -52,13 +52,17 @@ function createParser(
|
||||
}
|
||||
|
||||
export function LlamaIndexStream(
|
||||
res: AsyncIterable<Response>,
|
||||
response: StreamingAgentChatResponse | AsyncIterable<Response>,
|
||||
opts?: {
|
||||
callbacks?: AIStreamCallbacksAndOptions;
|
||||
parserOptions?: ParserOptions;
|
||||
},
|
||||
): { stream: ReadableStream; data: experimental_StreamData } {
|
||||
const data = new experimental_StreamData();
|
||||
const res =
|
||||
response instanceof StreamingAgentChatResponse
|
||||
? response.response
|
||||
: response;
|
||||
return {
|
||||
stream: createParser(res, data, opts?.parserOptions)
|
||||
.pipeThrough(createCallbacksTransformer(opts?.callbacks))
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import express from "express";
|
||||
import express, { Router } from "express";
|
||||
import { chatRequest } from "../controllers/chat-request.controller";
|
||||
import { chat } from "../controllers/chat.controller";
|
||||
|
||||
const llmRouter = express.Router();
|
||||
const llmRouter: Router = express.Router();
|
||||
|
||||
llmRouter.route("/").post(chat);
|
||||
llmRouter.route("/request").post(chatRequest);
|
||||
|
||||
export default llmRouter;
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node"
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ poetry install
|
||||
poetry shell
|
||||
```
|
||||
|
||||
By default, we use the OpenAI LLM (though you can customize, see `app/settings.py`). As a result you need to specify an `OPENAI_API_KEY` in an .env file in this directory.
|
||||
By default, we use the OpenAI LLM (though you can customize, see `app/settings.py`). As a result, you need to specify an `OPENAI_API_KEY` in an .env file in this directory.
|
||||
|
||||
Example `.env` file:
|
||||
|
||||
@@ -19,6 +19,8 @@ Example `.env` file:
|
||||
OPENAI_API_KEY=<openai_api_key>
|
||||
```
|
||||
|
||||
If you are using any tools or data sources, you can update their config files in the `config` folder.
|
||||
|
||||
Second, generate the embeddings of the documents in the `./data` directory (if this folder exists - otherwise, skip this step):
|
||||
|
||||
```
|
||||
@@ -31,7 +33,12 @@ Third, run the development server:
|
||||
python main.py
|
||||
```
|
||||
|
||||
Then call the API endpoint `/api/chat` to see the result:
|
||||
The example provides two different API endpoints:
|
||||
|
||||
1. `/api/chat` - a streaming chat endpoint
|
||||
2. `/api/chat/request` - a non-streaming chat endpoint
|
||||
|
||||
You can test the streaming endpoint with the following curl request:
|
||||
|
||||
```
|
||||
curl --location 'localhost:8000/api/chat' \
|
||||
@@ -39,7 +46,15 @@ curl --location 'localhost:8000/api/chat' \
|
||||
--data '{ "messages": [{ "role": "user", "content": "Hello" }] }'
|
||||
```
|
||||
|
||||
You can start editing the API by modifying `app/api/routers/chat.py`. The endpoint auto-updates as you save the file.
|
||||
And for the non-streaming endpoint run:
|
||||
|
||||
```
|
||||
curl --location 'localhost:8000/api/chat/request' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{ "messages": [{ "role": "user", "content": "Hello" }] }'
|
||||
```
|
||||
|
||||
You can start editing the API endpoints by modifying `app/api/routers/chat.py`. The endpoints auto-update as you save the file. You can delete the endpoint you're not using.
|
||||
|
||||
Open [http://localhost:8000/docs](http://localhost:8000/docs) with your browser to see the Swagger UI of the API.
|
||||
|
||||
@@ -49,6 +64,40 @@ The API allows CORS for all origins to simplify development. You can change this
|
||||
ENVIRONMENT=prod python main.py
|
||||
```
|
||||
|
||||
## Using Docker
|
||||
|
||||
1. Build an image for the FastAPI app:
|
||||
|
||||
```
|
||||
docker build -t <your_backend_image_name> .
|
||||
```
|
||||
|
||||
2. Generate embeddings:
|
||||
|
||||
Parse the data and generate the vector embeddings if the `./data` folder exists - otherwise, skip this step:
|
||||
|
||||
```
|
||||
docker run \
|
||||
--rm \
|
||||
-v $(pwd)/.env:/app/.env \ # Use ENV variables and configuration from your file-system
|
||||
-v $(pwd)/config:/app/config \
|
||||
-v $(pwd)/data:/app/data \ # Use your local folder to read the data
|
||||
-v $(pwd)/storage:/app/storage \ # Use your file system to store the vector database
|
||||
<your_backend_image_name> \
|
||||
python app/engine/generate.py
|
||||
```
|
||||
|
||||
3. Start the API:
|
||||
|
||||
```
|
||||
docker run \
|
||||
-v $(pwd)/.env:/app/.env \ # Use ENV variables and configuration from your file-system
|
||||
-v $(pwd)/config:/app/config \
|
||||
-v $(pwd)/storage:/app/storage \ # Use your file system to store gea vector database
|
||||
-p 8000:8000 \
|
||||
<your_backend_image_name>
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about LlamaIndex, take a look at the following resources:
|
||||
|
||||
@@ -5,6 +5,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from llama_index.core.chat_engine.types import BaseChatEngine
|
||||
from llama_index.core.llms import ChatMessage, MessageRole
|
||||
from app.engine import get_chat_engine
|
||||
from typing import List, Tuple
|
||||
|
||||
chat_router = r = APIRouter()
|
||||
|
||||
@@ -18,20 +19,19 @@ class _ChatData(BaseModel):
|
||||
messages: List[_Message]
|
||||
|
||||
|
||||
@r.post("")
|
||||
async def chat(
|
||||
request: Request,
|
||||
data: _ChatData,
|
||||
chat_engine: BaseChatEngine = Depends(get_chat_engine),
|
||||
):
|
||||
class _Result(BaseModel):
|
||||
result: _Message
|
||||
|
||||
|
||||
async def parse_chat_data(data: _ChatData) -> Tuple[str, List[ChatMessage]]:
|
||||
# check preconditions and get last message
|
||||
if len(data.messages) == 0:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="No messages provided",
|
||||
)
|
||||
lastMessage = data.messages.pop()
|
||||
if lastMessage.role != MessageRole.USER:
|
||||
last_message = data.messages.pop()
|
||||
if last_message.role != MessageRole.USER:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Last message must be from user",
|
||||
@@ -44,16 +44,38 @@ async def chat(
|
||||
)
|
||||
for m in data.messages
|
||||
]
|
||||
return last_message.content, messages
|
||||
|
||||
# query chat engine
|
||||
response = await chat_engine.astream_chat(lastMessage.content, messages)
|
||||
|
||||
# stream response
|
||||
# streaming endpoint - delete if not needed
|
||||
@r.post("")
|
||||
async def chat(
|
||||
request: Request,
|
||||
data: _ChatData,
|
||||
chat_engine: BaseChatEngine = Depends(get_chat_engine),
|
||||
):
|
||||
last_message_content, messages = await parse_chat_data(data)
|
||||
|
||||
response = await chat_engine.astream_chat(last_message_content, messages)
|
||||
|
||||
async def event_generator():
|
||||
async for token in response.async_response_gen():
|
||||
# If client closes connection, stop sending events
|
||||
if await request.is_disconnected():
|
||||
break
|
||||
yield token
|
||||
|
||||
return StreamingResponse(event_generator(), media_type="text/plain")
|
||||
|
||||
|
||||
# non-streaming endpoint - delete if not needed
|
||||
@r.post("/request")
|
||||
async def chat_request(
|
||||
data: _ChatData,
|
||||
chat_engine: BaseChatEngine = Depends(get_chat_engine),
|
||||
) -> _Result:
|
||||
last_message_content, messages = await parse_chat_data(data)
|
||||
|
||||
response = await chat_engine.achat(last_message_content, messages)
|
||||
return _Result(
|
||||
result=_Message(role=MessageRole.ASSISTANT, content=response.response)
|
||||
)
|
||||
|
||||
@@ -10,7 +10,9 @@ python = "^3.11,<3.12"
|
||||
fastapi = "^0.109.1"
|
||||
uvicorn = { extras = ["standard"], version = "^0.23.2" }
|
||||
python-dotenv = "^1.0.0"
|
||||
llama-index = "^0.10.7"
|
||||
llama-index = "0.10.15"
|
||||
llama-index-core = "0.10.15"
|
||||
llama-index-agent-openai = "0.1.5"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
||||
@@ -8,7 +8,13 @@ First, install the dependencies:
|
||||
npm install
|
||||
```
|
||||
|
||||
Second, run the development server:
|
||||
Second, generate the embeddings of the documents in the `./data` directory (if this folder exists - otherwise, skip this step):
|
||||
|
||||
```
|
||||
npm run generate
|
||||
```
|
||||
|
||||
Third, run the development server:
|
||||
|
||||
```
|
||||
npm run dev
|
||||
@@ -20,6 +26,41 @@ You can start editing the page by modifying `app/page.tsx`. The page auto-update
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Using Docker
|
||||
|
||||
1. Build an image for the Next.js app:
|
||||
|
||||
```
|
||||
docker build -t <your_app_image_name> .
|
||||
```
|
||||
|
||||
2. Generate embeddings:
|
||||
|
||||
Parse the data and generate the vector embeddings if the `./data` folder exists - otherwise, skip this step:
|
||||
|
||||
```
|
||||
docker run \
|
||||
--rm \
|
||||
-v $(pwd)/.env:/app/.env \ # Use ENV variables and configuration from your file-system
|
||||
-v $(pwd)/config:/app/config \
|
||||
-v $(pwd)/data:/app/data \
|
||||
-v $(pwd)/cache:/app/cache \ # Use your file system to store the vector database
|
||||
<your_app_image_name> \
|
||||
npm run generate
|
||||
```
|
||||
|
||||
3. Start the app:
|
||||
|
||||
```
|
||||
docker run \
|
||||
--rm \
|
||||
-v $(pwd)/.env:/app/.env \ # Use ENV variables and configuration from your file-system
|
||||
-v $(pwd)/config:/app/config \
|
||||
-v $(pwd)/cache:/app/cache \ # Use your file system to store gea vector database
|
||||
-p 3000:3000 \
|
||||
<your_app_image_name>
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about LlamaIndex, take a look at the following resources:
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { LLM, SimpleChatEngine } from "llamaindex";
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
return new SimpleChatEngine({
|
||||
llm,
|
||||
});
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
trimStartOfStreamHelper,
|
||||
type AIStreamCallbacksAndOptions,
|
||||
} from "ai";
|
||||
import { Response } from "llamaindex";
|
||||
import { Response, StreamingAgentChatResponse } from "llamaindex";
|
||||
|
||||
type ParserOptions = {
|
||||
image_url?: string;
|
||||
@@ -52,13 +52,17 @@ function createParser(
|
||||
}
|
||||
|
||||
export function LlamaIndexStream(
|
||||
res: AsyncIterable<Response>,
|
||||
response: StreamingAgentChatResponse | AsyncIterable<Response>,
|
||||
opts?: {
|
||||
callbacks?: AIStreamCallbacksAndOptions;
|
||||
parserOptions?: ParserOptions;
|
||||
},
|
||||
): { stream: ReadableStream; data: experimental_StreamData } {
|
||||
const data = new experimental_StreamData();
|
||||
const res =
|
||||
response instanceof StreamingAgentChatResponse
|
||||
? response.response
|
||||
: response;
|
||||
return {
|
||||
stream: createParser(res, data, opts?.parserOptions)
|
||||
.pipeThrough(createCallbacksTransformer(opts?.callbacks))
|
||||
|
||||
@@ -2,7 +2,7 @@ import { initObservability } from "@/app/observability";
|
||||
import { StreamingTextResponse } from "ai";
|
||||
import { ChatMessage, MessageContent, OpenAI } from "llamaindex";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { createChatEngine } from "./engine";
|
||||
import { createChatEngine } from "./engine/chat";
|
||||
import { LlamaIndexStream } from "./llamaindex-stream";
|
||||
|
||||
initObservability();
|
||||
|
||||
@@ -15,7 +15,7 @@ const MemoizedReactMarkdown: FC<Options> = memo(
|
||||
export default function Markdown({ content }: { content: string }) {
|
||||
return (
|
||||
<MemoizedReactMarkdown
|
||||
className="prose dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 break-words"
|
||||
className="prose dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 break-words custom-markdown"
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
components={{
|
||||
p({ children }) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import "./markdown.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/* Custom CSS for chat message markdown */
|
||||
.custom-markdown ul {
|
||||
list-style-type: disc;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.custom-markdown ol {
|
||||
list-style-type: decimal;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.custom-markdown li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.custom-markdown ol ol {
|
||||
list-style: lower-alpha;
|
||||
}
|
||||
|
||||
.custom-markdown ul ul,
|
||||
.custom-markdown ol ol {
|
||||
margin-left: 20px;
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
"extends": ["next/core-web-vitals", "prettier"],
|
||||
"rules": {
|
||||
"max-params": ["error", 4],
|
||||
"prefer-const": "error"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
"name": "llama-index-nextjs-streaming",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"format": "prettier --ignore-unknown --cache --check .",
|
||||
"format:write": "prettier --ignore-unknown --write .",
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
@@ -38,6 +40,9 @@
|
||||
"tailwindcss": "^3.3.6",
|
||||
"typescript": "^5.3.2",
|
||||
"@types/react-syntax-highlighter": "^15.5.11",
|
||||
"cross-env": "^7.0.3"
|
||||
"cross-env": "^7.0.3",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"eslint-config-prettier": "^8.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
plugins: ["prettier-plugin-organize-imports"],
|
||||
};
|
||||
Reference in New Issue
Block a user