mirror of
https://github.com/run-llama/create-llama.git
synced 2026-07-19 23:13:36 -04:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a21dc86eaa | |||
| 83681febd4 | |||
| 8ebd1651b3 | |||
| 4cbe8c6641 | |||
| 90e38358b3 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-llama": patch
|
||||
---
|
||||
|
||||
Let user select multiple datasources (URLs, files and folders)
|
||||
@@ -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
|
||||
@@ -1,21 +1,5 @@
|
||||
# create-llama
|
||||
|
||||
## 0.0.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 625ed4d: Support Astra VectorDB
|
||||
- 922e0ce: Remove UI question (use shadcn as default). Use `html` UI by calling create-llama with --ui html parameter
|
||||
- ce2f24d: Update loaders and tools config to yaml format (for Python)
|
||||
- e8db041: Let user select multiple datasources (URLs, files and folders)
|
||||
- c06d4af: Add nodes to the response (Python)
|
||||
- 29b17ee: Allow using agents without any data source
|
||||
- 665c26c: Add redirect to documentation page when accessing the base URL (FastAPI)
|
||||
- 78ded9e: Add Dockerfile templates for Typescript and Python
|
||||
- 99e758f: Merge non-streaming and streaming template to one
|
||||
- b3f2685: Add support for agent generation for Typescript
|
||||
- 2739714: Use a database (MySQL or PostgreSQL) as a data source
|
||||
|
||||
## 0.0.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+4
-4
@@ -29,6 +29,7 @@ export async function createApp({
|
||||
ui,
|
||||
appPath,
|
||||
packageManager,
|
||||
eslint,
|
||||
frontend,
|
||||
openAiKey,
|
||||
llamaCloudKey,
|
||||
@@ -77,6 +78,7 @@ export async function createApp({
|
||||
ui,
|
||||
packageManager,
|
||||
isOnline,
|
||||
eslint,
|
||||
openAiKey,
|
||||
llamaCloudKey,
|
||||
model,
|
||||
@@ -125,13 +127,11 @@ 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(
|
||||
configFile,
|
||||
`file://${root}/${configFile}`,
|
||||
"config/tools.json",
|
||||
`file://${root}/config/tools.json`,
|
||||
)} file.`,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -103,6 +103,7 @@ export async function runCreateLlama(
|
||||
"--open-ai-key",
|
||||
process.env.OPENAI_API_KEY,
|
||||
appType,
|
||||
"--eslint",
|
||||
"--use-pnpm",
|
||||
"--port",
|
||||
port,
|
||||
|
||||
@@ -48,20 +48,3 @@ export const copy = async (
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const assetRelocator = (name: string) => {
|
||||
switch (name) {
|
||||
case "gitignore":
|
||||
case "eslintrc.json": {
|
||||
return `.${name}`;
|
||||
}
|
||||
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
|
||||
// https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
|
||||
case "README-template.md": {
|
||||
return "README.md";
|
||||
}
|
||||
default: {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+1
-80
@@ -1,8 +1,6 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import yaml, { Document } from "yaml";
|
||||
import { templatesDir } from "./dir";
|
||||
import { DbSourceConfig, TemplateDataSource, WebSourceConfig } from "./types";
|
||||
import { TemplateDataSource } from "./types";
|
||||
|
||||
export const EXAMPLE_FILE: TemplateDataSource = {
|
||||
type: "file",
|
||||
@@ -30,80 +28,3 @@ export function getDataSources(
|
||||
}
|
||||
return dataSources;
|
||||
}
|
||||
|
||||
export async function writeLoadersConfig(
|
||||
root: string,
|
||||
dataSources: TemplateDataSource[],
|
||||
useLlamaParse?: boolean,
|
||||
) {
|
||||
if (dataSources.length === 0) return; // no datasources, no config needed
|
||||
const loaderConfig = new Document({});
|
||||
// Web loader config
|
||||
if (dataSources.some((ds) => ds.type === "web")) {
|
||||
const webLoaderConfig = new Document({});
|
||||
|
||||
// Create config for browser driver arguments
|
||||
const driverArgNodeValue = webLoaderConfig.createNode([
|
||||
"--no-sandbox",
|
||||
"--disable-dev-shm-usage",
|
||||
]);
|
||||
driverArgNodeValue.commentBefore =
|
||||
" The arguments to pass to the webdriver. E.g.: add --headless to run in headless mode";
|
||||
webLoaderConfig.set("driver_arguments", driverArgNodeValue);
|
||||
|
||||
// Create config for urls
|
||||
const urlConfigs = dataSources
|
||||
.filter((ds) => ds.type === "web")
|
||||
.map((ds) => {
|
||||
const dsConfig = ds.config as WebSourceConfig;
|
||||
return {
|
||||
base_url: dsConfig.baseUrl,
|
||||
prefix: dsConfig.prefix,
|
||||
depth: dsConfig.depth,
|
||||
};
|
||||
});
|
||||
const urlConfigNode = webLoaderConfig.createNode(urlConfigs);
|
||||
urlConfigNode.commentBefore = ` base_url: The URL to start crawling with
|
||||
prefix: Only crawl URLs matching the specified prefix
|
||||
depth: The maximum depth for BFS traversal
|
||||
You can add more websites by adding more entries (don't forget the - prefix from YAML)`;
|
||||
webLoaderConfig.set("urls", urlConfigNode);
|
||||
|
||||
// Add web config to the loaders config
|
||||
loaderConfig.set("web", webLoaderConfig);
|
||||
}
|
||||
|
||||
// File loader config
|
||||
if (dataSources.some((ds) => ds.type === "file")) {
|
||||
// Add documentation to web loader config
|
||||
const node = loaderConfig.createNode({
|
||||
use_llama_parse: useLlamaParse,
|
||||
});
|
||||
node.commentBefore = ` use_llama_parse: Use LlamaParse if \`true\`. Needs a \`LLAMA_CLOUD_API_KEY\` from https://cloud.llamaindex.ai set as environment variable`;
|
||||
loaderConfig.set("file", node);
|
||||
}
|
||||
|
||||
// DB loader config
|
||||
const dbLoaders = dataSources.filter((ds) => ds.type === "db");
|
||||
if (dbLoaders.length > 0) {
|
||||
const dbLoaderConfig = new Document({});
|
||||
const configEntries = dbLoaders.map((ds) => {
|
||||
const dsConfig = ds.config as DbSourceConfig;
|
||||
return {
|
||||
uri: dsConfig.uri,
|
||||
queries: [dsConfig.queries],
|
||||
};
|
||||
});
|
||||
|
||||
const node = dbLoaderConfig.createNode(configEntries);
|
||||
node.commentBefore = ` The configuration for the database loader, only supports MySQL and PostgreSQL databases for now.
|
||||
uri: The URI for the database. E.g.: mysql+pymysql://user:password@localhost:3306/db or postgresql+psycopg2://user:password@localhost:5432/db
|
||||
query: The query to fetch data from the database. E.g.: SELECT * FROM table`;
|
||||
loaderConfig.set("db", node);
|
||||
}
|
||||
|
||||
// Write loaders config
|
||||
const loaderConfigPath = path.join(root, "config", "loaders.yaml");
|
||||
await fs.mkdir(path.join(root, "config"), { recursive: true });
|
||||
await fs.writeFile(loaderConfigPath, yaml.stringify(loaderConfig));
|
||||
}
|
||||
|
||||
@@ -46,16 +46,13 @@ export const writeDevcontainer = async (
|
||||
framework: TemplateFramework,
|
||||
frontend: boolean,
|
||||
) => {
|
||||
const devcontainerDir = path.join(root, ".devcontainer");
|
||||
if (fs.existsSync(devcontainerDir)) {
|
||||
console.log("Template already has a .devcontainer. Using it.");
|
||||
return;
|
||||
}
|
||||
console.log("Adding .devcontainer");
|
||||
const devcontainerContent = renderDevcontainerContent(
|
||||
templatesDir,
|
||||
framework,
|
||||
frontend,
|
||||
);
|
||||
const devcontainerDir = path.join(root, ".devcontainer");
|
||||
fs.mkdirSync(devcontainerDir);
|
||||
await fs.promises.writeFile(
|
||||
path.join(devcontainerDir, "devcontainer.json"),
|
||||
|
||||
@@ -93,21 +93,6 @@ const getVectorDBEnvs = (vectorDb: TemplateVectorDB) => {
|
||||
description: "The password to access the Milvus server.",
|
||||
},
|
||||
];
|
||||
case "astra":
|
||||
return [
|
||||
{
|
||||
name: "ASTRA_DB_APPLICATION_TOKEN",
|
||||
description: "The generated app token for your Astra database",
|
||||
},
|
||||
{
|
||||
name: "ASTRA_DB_ENDPOINT",
|
||||
description: "The API endpoint for your Astra database",
|
||||
},
|
||||
{
|
||||
name: "ASTRA_DB_COLLECTION",
|
||||
description: "The name of the collection in your Astra database",
|
||||
},
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
+1
-19
@@ -4,14 +4,12 @@ import path from "path";
|
||||
import { cyan } from "picocolors";
|
||||
|
||||
import fsExtra from "fs-extra";
|
||||
import { writeLoadersConfig } from "./datasources";
|
||||
import { createBackendEnvFile, createFrontendEnvFile } from "./env-variables";
|
||||
import { PackageManager } from "./get-pkg-manager";
|
||||
import { installLlamapackProject } from "./llama-pack";
|
||||
import { isHavingPoetryLockFile, tryPoetryRun } from "./poetry";
|
||||
import { installPythonTemplate } from "./python";
|
||||
import { downloadAndExtractRepo } from "./repo";
|
||||
import { ConfigFileType, writeToolsConfig } from "./tools";
|
||||
import {
|
||||
FileSourceConfig,
|
||||
InstallTemplateArgs,
|
||||
@@ -119,23 +117,10 @@ export const installTemplate = async (
|
||||
|
||||
if (props.framework === "fastapi") {
|
||||
await installPythonTemplate(props);
|
||||
// write loaders configuration (currently Python only)
|
||||
await writeLoadersConfig(
|
||||
props.root,
|
||||
props.dataSources,
|
||||
props.useLlamaParse,
|
||||
);
|
||||
} else {
|
||||
await installTSTemplate(props);
|
||||
}
|
||||
|
||||
// write tools configuration
|
||||
await writeToolsConfig(
|
||||
props.root,
|
||||
props.tools,
|
||||
props.framework === "fastapi" ? ConfigFileType.YAML : ConfigFileType.JSON,
|
||||
);
|
||||
|
||||
if (props.backend) {
|
||||
// This is a backend, so we need to copy the test data and create the env file.
|
||||
|
||||
@@ -153,10 +138,7 @@ export const installTemplate = async (
|
||||
|
||||
if (props.dataSources.length > 0) {
|
||||
console.log("\nGenerating context data...\n");
|
||||
await copyContextData(
|
||||
props.root,
|
||||
props.dataSources.filter((ds) => ds.type === "file"),
|
||||
);
|
||||
await copyContextData(props.root, props.dataSources);
|
||||
if (
|
||||
props.postInstallAction === "runApp" ||
|
||||
props.postInstallAction === "dependencies"
|
||||
|
||||
+104
-62
@@ -3,8 +3,7 @@ import path from "path";
|
||||
import { cyan, red } from "picocolors";
|
||||
import { parse, stringify } from "smol-toml";
|
||||
import terminalLink from "terminal-link";
|
||||
|
||||
import { assetRelocator, copy } from "./copy";
|
||||
import { copy } from "./copy";
|
||||
import { templatesDir } from "./dir";
|
||||
import { isPoetryAvailable, tryPoetryInstall } from "./poetry";
|
||||
import { Tool } from "./tools";
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
InstallTemplateArgs,
|
||||
TemplateDataSource,
|
||||
TemplateVectorDB,
|
||||
WebSourceConfig,
|
||||
} from "./types";
|
||||
|
||||
interface Dependency {
|
||||
@@ -60,45 +60,21 @@ const getAdditionalDependencies = (
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "astra": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-astra-db",
|
||||
version: "^0.1.5",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add data source dependencies
|
||||
const dataSourceType = dataSource?.type;
|
||||
switch (dataSourceType) {
|
||||
case "file":
|
||||
dependencies.push({
|
||||
name: "docx2txt",
|
||||
version: "^0.8",
|
||||
});
|
||||
break;
|
||||
case "web":
|
||||
dependencies.push({
|
||||
name: "llama-index-readers-web",
|
||||
version: "^0.1.6",
|
||||
});
|
||||
break;
|
||||
case "db":
|
||||
dependencies.push({
|
||||
name: "llama-index-readers-database",
|
||||
version: "^0.1.3",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "pymysql",
|
||||
version: "^1.1.0",
|
||||
extras: ["rsa"],
|
||||
});
|
||||
dependencies.push({
|
||||
name: "psycopg2",
|
||||
version: "^2.9.9",
|
||||
});
|
||||
break;
|
||||
if (dataSourceType === "file") {
|
||||
// llama-index-readers-file (pdf, excel, csv) is already included in llama_index package
|
||||
dependencies.push({
|
||||
name: "docx2txt",
|
||||
version: "^0.8",
|
||||
});
|
||||
} else if (dataSourceType === "web") {
|
||||
dependencies.push({
|
||||
name: "llama-index-readers-web",
|
||||
version: "^0.1.6",
|
||||
});
|
||||
}
|
||||
|
||||
// Add tools dependencies
|
||||
@@ -222,38 +198,104 @@ export const installPythonTemplate = async ({
|
||||
await copy("**", root, {
|
||||
parents: true,
|
||||
cwd: templatePath,
|
||||
rename: assetRelocator,
|
||||
rename(name) {
|
||||
switch (name) {
|
||||
case "gitignore": {
|
||||
return `.${name}`;
|
||||
}
|
||||
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
|
||||
// https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
|
||||
case "README-template.md": {
|
||||
return "README.md";
|
||||
}
|
||||
default: {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
const enginePath = path.join(root, "app", "engine");
|
||||
|
||||
// Copy selected vector DB
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "vectordbs", "python", vectorDb ?? "none"),
|
||||
});
|
||||
if (dataSources.length > 0) {
|
||||
const enginePath = path.join(root, "app", "engine");
|
||||
|
||||
// Copy all loaders to enginePath
|
||||
const loaderPath = path.join(enginePath, "loaders");
|
||||
await copy("**", loaderPath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "loaders", "python"),
|
||||
});
|
||||
const vectorDbDirName = vectorDb ?? "none";
|
||||
const VectorDBPath = path.join(
|
||||
compPath,
|
||||
"vectordbs",
|
||||
"python",
|
||||
vectorDbDirName,
|
||||
);
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: VectorDBPath,
|
||||
});
|
||||
|
||||
// Select and copy engine code based on data sources and tools
|
||||
let engine;
|
||||
tools = tools ?? [];
|
||||
if (dataSources.length > 0 && tools.length === 0) {
|
||||
console.log("\nNo tools selected - use optimized context chat engine\n");
|
||||
engine = "chat";
|
||||
} else {
|
||||
engine = "agent";
|
||||
// Copy engine code
|
||||
if (tools !== undefined && tools.length > 0) {
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "python", "agent"),
|
||||
});
|
||||
// Write tool configs
|
||||
const configContent: Record<string, any> = {};
|
||||
tools.forEach((tool) => {
|
||||
configContent[tool.name] = tool.config ?? {};
|
||||
});
|
||||
const configFilePath = path.join(root, "config/tools.json");
|
||||
await fs.mkdir(path.join(root, "config"), { recursive: true });
|
||||
await fs.writeFile(
|
||||
configFilePath,
|
||||
JSON.stringify(configContent, null, 2),
|
||||
);
|
||||
} else {
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "python", "chat"),
|
||||
});
|
||||
}
|
||||
|
||||
const loaderConfigs: Record<string, any> = {};
|
||||
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 = 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,
|
||||
};
|
||||
});
|
||||
loaderConfigs["web"] = webLoaderConfig;
|
||||
}
|
||||
// File loader config
|
||||
if (dataSources.some((ds) => ds.type === "file")) {
|
||||
loaderConfigs["file"] = {
|
||||
use_llama_parse: useLlamaParse,
|
||||
};
|
||||
}
|
||||
// Write loaders config
|
||||
if (Object.keys(loaderConfigs).length > 0) {
|
||||
const loaderConfigPath = path.join(root, "config/loaders.json");
|
||||
await fs.mkdir(path.join(root, "config"), { recursive: true });
|
||||
await fs.writeFile(
|
||||
loaderConfigPath,
|
||||
JSON.stringify(loaderConfigs, null, 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "python", engine),
|
||||
});
|
||||
|
||||
const addOnDependencies = dataSources
|
||||
.map((ds) => getAdditionalDependencies(vectorDb, ds, tools))
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { red } from "picocolors";
|
||||
import yaml from "yaml";
|
||||
import { makeDir } from "./make-dir";
|
||||
import { TemplateFramework } from "./types";
|
||||
|
||||
export type Tool = {
|
||||
@@ -12,7 +8,6 @@ export type Tool = {
|
||||
dependencies?: ToolDependencies[];
|
||||
supportedFrameworks?: Array<TemplateFramework>;
|
||||
};
|
||||
|
||||
export type ToolDependencies = {
|
||||
name: string;
|
||||
version?: string;
|
||||
@@ -78,33 +73,3 @@ export const toolsRequireConfig = (tools?: Tool[]): boolean => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export enum ConfigFileType {
|
||||
YAML = "yaml",
|
||||
JSON = "json",
|
||||
}
|
||||
|
||||
export const writeToolsConfig = async (
|
||||
root: string,
|
||||
tools: Tool[] = [],
|
||||
type: ConfigFileType = ConfigFileType.YAML,
|
||||
) => {
|
||||
if (tools.length === 0) return; // no tools selected, no config need
|
||||
const configContent: Record<string, any> = {};
|
||||
tools.forEach((tool) => {
|
||||
configContent[tool.name] = tool.config ?? {};
|
||||
});
|
||||
const configPath = path.join(root, "config");
|
||||
await makeDir(configPath);
|
||||
if (type === ConfigFileType.YAML) {
|
||||
await fs.writeFile(
|
||||
path.join(configPath, "tools.yaml"),
|
||||
yaml.stringify(configContent),
|
||||
);
|
||||
} else {
|
||||
await fs.writeFile(
|
||||
path.join(configPath, "tools.json"),
|
||||
JSON.stringify(configContent, null, 2),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
+4
-16
@@ -4,13 +4,7 @@ import { Tool } from "./tools";
|
||||
export type TemplateType = "streaming" | "community" | "llamapack";
|
||||
export type TemplateFramework = "nextjs" | "express" | "fastapi";
|
||||
export type TemplateUI = "html" | "shadcn";
|
||||
export type TemplateVectorDB =
|
||||
| "none"
|
||||
| "mongo"
|
||||
| "pg"
|
||||
| "pinecone"
|
||||
| "milvus"
|
||||
| "astra";
|
||||
export type TemplateVectorDB = "none" | "mongo" | "pg" | "pinecone" | "milvus";
|
||||
export type TemplatePostInstallAction =
|
||||
| "none"
|
||||
| "VSCode"
|
||||
@@ -20,7 +14,7 @@ export type TemplateDataSource = {
|
||||
type: TemplateDataSourceType;
|
||||
config: TemplateDataSourceConfig;
|
||||
};
|
||||
export type TemplateDataSourceType = "file" | "web" | "db";
|
||||
export type TemplateDataSourceType = "file" | "web";
|
||||
export type TemplateObservability = "none" | "opentelemetry";
|
||||
// Config for both file and folder
|
||||
export type FileSourceConfig = {
|
||||
@@ -31,15 +25,8 @@ export type WebSourceConfig = {
|
||||
prefix?: string;
|
||||
depth?: number;
|
||||
};
|
||||
export type DbSourceConfig = {
|
||||
uri?: string;
|
||||
queries?: string;
|
||||
};
|
||||
|
||||
export type TemplateDataSourceConfig =
|
||||
| FileSourceConfig
|
||||
| WebSourceConfig
|
||||
| DbSourceConfig;
|
||||
export type TemplateDataSourceConfig = FileSourceConfig | WebSourceConfig;
|
||||
|
||||
export type CommunityProjectConfig = {
|
||||
owner: string;
|
||||
@@ -57,6 +44,7 @@ export interface InstallTemplateArgs {
|
||||
framework: TemplateFramework;
|
||||
ui: TemplateUI;
|
||||
dataSources: TemplateDataSource[];
|
||||
eslint: boolean;
|
||||
customApiPath?: string;
|
||||
openAiKey?: string;
|
||||
llamaCloudKey?: string;
|
||||
|
||||
+129
-96
@@ -2,12 +2,50 @@ import fs from "fs/promises";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { bold, cyan } from "picocolors";
|
||||
import { assetRelocator, copy } from "../helpers/copy";
|
||||
import { copy } from "../helpers/copy";
|
||||
import { callPackageManager } from "../helpers/install";
|
||||
import { templatesDir } from "./dir";
|
||||
import { PackageManager } from "./get-pkg-manager";
|
||||
import { InstallTemplateArgs } from "./types";
|
||||
|
||||
const rename = (name: string) => {
|
||||
switch (name) {
|
||||
case "gitignore":
|
||||
case "eslintrc.json": {
|
||||
return `.${name}`;
|
||||
}
|
||||
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
|
||||
// https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
|
||||
case "README-template.md": {
|
||||
return "README.md";
|
||||
}
|
||||
default: {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const installTSDependencies = async (
|
||||
packageJson: any,
|
||||
packageManager: PackageManager,
|
||||
isOnline: boolean,
|
||||
): Promise<void> => {
|
||||
console.log("\nInstalling dependencies:");
|
||||
for (const dependency in packageJson.dependencies)
|
||||
console.log(`- ${cyan(dependency)}`);
|
||||
|
||||
console.log("\nInstalling devDependencies:");
|
||||
for (const dependency in packageJson.devDependencies)
|
||||
console.log(`- ${cyan(dependency)}`);
|
||||
|
||||
console.log();
|
||||
|
||||
await callPackageManager(packageManager, isOnline).catch((error) => {
|
||||
console.error("Failed to install TS dependencies. Exiting...");
|
||||
process.exit(1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Install a LlamaIndex internal template to a given `root` directory.
|
||||
*/
|
||||
@@ -19,6 +57,8 @@ export const installTSTemplate = async ({
|
||||
template,
|
||||
framework,
|
||||
ui,
|
||||
eslint,
|
||||
customApiPath,
|
||||
vectorDb,
|
||||
postInstallAction,
|
||||
backend,
|
||||
@@ -35,11 +75,12 @@ 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,
|
||||
cwd: templatePath,
|
||||
rename: assetRelocator,
|
||||
rename,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -97,6 +138,9 @@ export const installTSTemplate = async ({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the selected chat engine files to the target directory and reference it.
|
||||
*/
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
const relativeEngineDestPath =
|
||||
framework === "nextjs"
|
||||
@@ -104,33 +148,58 @@ export const installTSTemplate = async ({
|
||||
: path.join("src", "controllers");
|
||||
const enginePath = path.join(root, relativeEngineDestPath, "engine");
|
||||
|
||||
// copy vector db component
|
||||
console.log("\nUsing vector DB:", vectorDb, "\n");
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "vectordbs", "typescript", vectorDb ?? "none"),
|
||||
});
|
||||
|
||||
// copy loader component (TS only supports llama_parse and file for now)
|
||||
const loaderFolder = useLlamaParse ? "llama_parse" : "file";
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "loaders", "typescript", loaderFolder),
|
||||
});
|
||||
|
||||
// Select and copy engine code based on data sources and tools
|
||||
let engine;
|
||||
tools = tools ?? [];
|
||||
if (dataSources.length > 0 && tools.length === 0) {
|
||||
console.log("\nNo tools selected - use optimized context chat engine\n");
|
||||
engine = "chat";
|
||||
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 {
|
||||
engine = "agent";
|
||||
if (vectorDb) {
|
||||
// copy vector db component
|
||||
console.log("\nUsing vector DB:", vectorDb, "\n");
|
||||
const vectorDBPath = path.join(
|
||||
compPath,
|
||||
"vectordbs",
|
||||
"typescript",
|
||||
vectorDb,
|
||||
);
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
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 tools_config.json
|
||||
const configContent: Record<string, any> = {};
|
||||
tools.forEach((tool) => {
|
||||
configContent[tool.name] = tool.config ?? {};
|
||||
});
|
||||
const configFilePath = path.join(enginePath, "tools_config.json");
|
||||
await fs.writeFile(
|
||||
configFilePath,
|
||||
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"),
|
||||
});
|
||||
}
|
||||
}
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "typescript", engine),
|
||||
});
|
||||
|
||||
/**
|
||||
* Copy the selected UI files to the target directory and reference it.
|
||||
@@ -145,54 +214,13 @@ export const installTSTemplate = async ({
|
||||
await copy("**", destUiPath, {
|
||||
parents: true,
|
||||
cwd: uiPath,
|
||||
rename: assetRelocator,
|
||||
rename,
|
||||
});
|
||||
}
|
||||
|
||||
/** Modify frontend code to use custom API path */
|
||||
if (framework === "nextjs" && !backend) {
|
||||
console.log(
|
||||
"\nUsing external API for frontend, removing API code and configuration\n",
|
||||
);
|
||||
// remove the default api folder and config folder
|
||||
await fs.rm(path.join(root, "app", "api"), { recursive: true });
|
||||
await fs.rm(path.join(root, "config"), { recursive: true, force: true });
|
||||
}
|
||||
|
||||
const packageJson = await updatePackageJson({
|
||||
root,
|
||||
appName,
|
||||
dataSources,
|
||||
relativeEngineDestPath,
|
||||
framework,
|
||||
ui,
|
||||
observability,
|
||||
});
|
||||
|
||||
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
|
||||
await installTSDependencies(packageJson, packageManager, isOnline);
|
||||
}
|
||||
|
||||
// Copy deployment files for typescript
|
||||
await copy("**", root, {
|
||||
cwd: path.join(compPath, "deployments", "typescript"),
|
||||
});
|
||||
};
|
||||
|
||||
async function updatePackageJson({
|
||||
root,
|
||||
appName,
|
||||
dataSources,
|
||||
relativeEngineDestPath,
|
||||
framework,
|
||||
ui,
|
||||
observability,
|
||||
}: Pick<
|
||||
InstallTemplateArgs,
|
||||
"root" | "appName" | "dataSources" | "framework" | "ui" | "observability"
|
||||
> & {
|
||||
relativeEngineDestPath: string;
|
||||
}): Promise<any> {
|
||||
/**
|
||||
* Update the package.json scripts.
|
||||
*/
|
||||
const packageJsonFile = path.join(root, "package.json");
|
||||
const packageJson: any = JSON.parse(
|
||||
await fs.readFile(packageJsonFile, "utf8"),
|
||||
@@ -200,15 +228,26 @@ async function updatePackageJson({
|
||||
packageJson.name = appName;
|
||||
packageJson.version = "0.1.0";
|
||||
|
||||
if (framework === "nextjs" && customApiPath) {
|
||||
console.log(
|
||||
"\nUsing external API with custom API path:",
|
||||
customApiPath,
|
||||
"\n",
|
||||
);
|
||||
// remove the default api folder
|
||||
const apiPath = path.join(root, "app", "api");
|
||||
await fs.rm(apiPath, { recursive: true });
|
||||
// modify the dev script to use the custom api path
|
||||
}
|
||||
|
||||
if (dataSources.length > 0 && relativeEngineDestPath) {
|
||||
// TODO: move script to {root}/scripts for all frameworks
|
||||
// add generate script if using context engine
|
||||
packageJson.scripts = {
|
||||
...packageJson.scripts,
|
||||
generate: `ts-node ${path.join(
|
||||
generate: `node ${path.join(
|
||||
relativeEngineDestPath,
|
||||
"engine",
|
||||
"generate.ts",
|
||||
"generate.mjs",
|
||||
)}`,
|
||||
};
|
||||
}
|
||||
@@ -248,31 +287,25 @@ async function updatePackageJson({
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
return packageJson;
|
||||
}
|
||||
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
|
||||
await installTSDependencies(packageJson, packageManager, isOnline);
|
||||
}
|
||||
|
||||
async function installTSDependencies(
|
||||
packageJson: any,
|
||||
packageManager: PackageManager,
|
||||
isOnline: boolean,
|
||||
): Promise<void> {
|
||||
console.log("\nInstalling dependencies:");
|
||||
for (const dependency in packageJson.dependencies)
|
||||
console.log(`- ${cyan(dependency)}`);
|
||||
|
||||
console.log("\nInstalling devDependencies:");
|
||||
for (const dependency in packageJson.devDependencies)
|
||||
console.log(`- ${cyan(dependency)}`);
|
||||
|
||||
console.log();
|
||||
|
||||
await callPackageManager(packageManager, isOnline).catch((error) => {
|
||||
console.error("Failed to install TS dependencies. Exiting...");
|
||||
process.exit(1);
|
||||
// Copy deployment files for typescript
|
||||
await copy("**", root, {
|
||||
cwd: path.join(compPath, "deployments", "typescript"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -32,6 +32,13 @@ const program = new Commander.Command(packageJson.name)
|
||||
.action((name) => {
|
||||
projectPath = name;
|
||||
})
|
||||
.option(
|
||||
"--eslint",
|
||||
`
|
||||
|
||||
Initialize with eslint config.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--use-npm",
|
||||
`
|
||||
@@ -182,6 +189,9 @@ 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 = [];
|
||||
@@ -286,6 +296,7 @@ async function run(): Promise<void> {
|
||||
ui: program.ui,
|
||||
appPath: resolvedProjectPath,
|
||||
packageManager,
|
||||
eslint: program.eslint,
|
||||
frontend: program.frontend,
|
||||
openAiKey: program.openAiKey,
|
||||
llamaCloudKey: program.llamaCloudKey,
|
||||
|
||||
+2
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-llama",
|
||||
"version": "0.0.32",
|
||||
"version": "0.0.31",
|
||||
"keywords": [
|
||||
"rag",
|
||||
"llamaindex",
|
||||
@@ -71,8 +71,7 @@
|
||||
"typescript": "^5.3.3",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"ora": "^8.0.1",
|
||||
"fs-extra": "11.2.0",
|
||||
"yaml": "2.4.1"
|
||||
"fs-extra": "11.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14.0"
|
||||
|
||||
Generated
-9
@@ -113,9 +113,6 @@ devDependencies:
|
||||
wait-port:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
yaml:
|
||||
specifier: 2.4.1
|
||||
version: 2.4.1
|
||||
|
||||
packages:
|
||||
|
||||
@@ -3369,12 +3366,6 @@ 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'}
|
||||
|
||||
+89
-96
@@ -70,7 +70,8 @@ if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK)
|
||||
const defaults: QuestionArgs = {
|
||||
template: "streaming",
|
||||
framework: "nextjs",
|
||||
ui: "shadcn",
|
||||
ui: "html",
|
||||
eslint: true,
|
||||
frontend: false,
|
||||
openAiKey: "",
|
||||
llamaCloudKey: "",
|
||||
@@ -101,7 +102,6 @@ const getVectorDbChoices = (framework: TemplateFramework) => {
|
||||
{ title: "PostgreSQL", value: "pg" },
|
||||
{ title: "Pinecone", value: "pinecone" },
|
||||
{ title: "Milvus", value: "milvus" },
|
||||
{ title: "Astra", value: "astra" },
|
||||
];
|
||||
|
||||
const vectordbLang = framework === "fastapi" ? "python" : "typescript";
|
||||
@@ -132,7 +132,7 @@ export const getDataSourceChoices = (
|
||||
}
|
||||
if (selectedDataSource === undefined || selectedDataSource.length === 0) {
|
||||
choices.push({
|
||||
title: "No data, just a simple chat or agent",
|
||||
title: "No data, just a simple chat",
|
||||
value: "none",
|
||||
});
|
||||
choices.push({
|
||||
@@ -160,10 +160,6 @@ export const getDataSourceChoices = (
|
||||
title: "Use website content (requires Chrome)",
|
||||
value: "web",
|
||||
});
|
||||
choices.push({
|
||||
title: "Use data from a database (Mysql, PostgreSQL)",
|
||||
value: "db",
|
||||
});
|
||||
}
|
||||
return choices;
|
||||
};
|
||||
@@ -506,7 +502,25 @@ export const askQuestions = async (
|
||||
|
||||
if (program.framework === "nextjs" || program.frontend) {
|
||||
if (!program.ui) {
|
||||
program.ui = defaults.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,19 +627,19 @@ export const askQuestions = async (
|
||||
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: "select",
|
||||
name: "selectedSource",
|
||||
message: firstQuestion
|
||||
? "Which data source would you like to use?"
|
||||
: "Would you like to add another data source?",
|
||||
message:
|
||||
program.dataSources.length === 0
|
||||
? "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,
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
@@ -634,93 +648,52 @@ export const askQuestions = async (
|
||||
// user doesn't want another data source or any data source
|
||||
break;
|
||||
}
|
||||
switch (selectedSource) {
|
||||
case "exampleFile": {
|
||||
program.dataSources.push(EXAMPLE_FILE);
|
||||
break;
|
||||
}
|
||||
case "file":
|
||||
case "folder": {
|
||||
const selectedPaths = await selectLocalContextData(selectedSource);
|
||||
for (const p of selectedPaths) {
|
||||
program.dataSources.push({
|
||||
type: "file",
|
||||
config: {
|
||||
path: p,
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "web": {
|
||||
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,
|
||||
);
|
||||
|
||||
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: "web",
|
||||
type: "file",
|
||||
config: {
|
||||
baseUrl,
|
||||
prefix: baseUrl,
|
||||
depth: 1,
|
||||
path: p,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "db": {
|
||||
const dbPrompts: prompts.PromptObject<string>[] = [
|
||||
{
|
||||
type: "text",
|
||||
name: "uri",
|
||||
message:
|
||||
"Please enter the connection string (URI) for the database.",
|
||||
initial: "mysql+pymysql://user:pass@localhost:3306/mydb",
|
||||
validate: (value: string) => {
|
||||
if (!value) {
|
||||
return "Please provide a valid connection string";
|
||||
} else if (
|
||||
!(
|
||||
value.startsWith("mysql+pymysql://") ||
|
||||
value.startsWith("postgresql+psycopg://")
|
||||
)
|
||||
) {
|
||||
return "The connection string must start with 'mysql+pymysql://' for MySQL or 'postgresql+psycopg://' for PostgreSQL";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
} 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;
|
||||
},
|
||||
// Only ask for a query, user can provide more complex queries in the config file later
|
||||
{
|
||||
type: (prev) => (prev ? "text" : null),
|
||||
name: "queries",
|
||||
message: "Please enter the SQL query to fetch data:",
|
||||
initial: "SELECT * FROM mytable",
|
||||
},
|
||||
];
|
||||
program.dataSources.push({
|
||||
type: "db",
|
||||
config: await prompts(dbPrompts, handlers),
|
||||
});
|
||||
}
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
|
||||
program.dataSources.push({
|
||||
type: "web",
|
||||
config: {
|
||||
baseUrl,
|
||||
prefix: baseUrl,
|
||||
depth: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -741,7 +714,7 @@ export const askQuestions = async (
|
||||
name: "useLlamaParse",
|
||||
message:
|
||||
"Would you like to use LlamaParse (improved parser for RAG - requires API key)?",
|
||||
initial: false,
|
||||
initial: true,
|
||||
active: "yes",
|
||||
inactive: "no",
|
||||
},
|
||||
@@ -784,7 +757,8 @@ export const askQuestions = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.tools) {
|
||||
// TODO: allow tools also without datasources
|
||||
if (!program.tools && program.dataSources.length > 0) {
|
||||
if (ciInfo.isCI) {
|
||||
program.tools = getPrefOrDefault("tools");
|
||||
} else {
|
||||
@@ -810,5 +784,24 @@ 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();
|
||||
};
|
||||
|
||||
@@ -10,10 +10,6 @@ RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python
|
||||
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
|
||||
|
||||
@@ -3,14 +3,17 @@ FROM node:20-alpine as build
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package.json package-lock.* ./
|
||||
RUN npm install
|
||||
COPY package.json pnpm-lock.yaml* /app/
|
||||
RUN npm install -g pnpm
|
||||
RUN pnpm install
|
||||
|
||||
# Build the application
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
RUN pnpm run build
|
||||
|
||||
# ====================================
|
||||
FROM build as release
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
RUN chmod +x start.sh
|
||||
|
||||
CMD ["/bin/sh", "start.sh"]
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$ENVIRONMENT" = "dev" ]; then
|
||||
echo "Running Development mode"
|
||||
pnpm dev
|
||||
else
|
||||
echo "Running Production mode"
|
||||
pnpm build
|
||||
pnpm start
|
||||
fi
|
||||
@@ -11,12 +11,11 @@ def get_chat_engine():
|
||||
top_k = os.getenv("TOP_K", "3")
|
||||
tools = []
|
||||
|
||||
# Add query tool if index exists
|
||||
# Add query tool
|
||||
index = get_index()
|
||||
if index is not None:
|
||||
query_engine = index.as_query_engine(similarity_top_k=int(top_k))
|
||||
query_engine_tool = QueryEngineTool.from_defaults(query_engine=query_engine)
|
||||
tools.append(query_engine_tool)
|
||||
query_engine = index.as_query_engine(similarity_top_k=int(top_k))
|
||||
query_engine_tool = QueryEngineTool.from_defaults(query_engine=query_engine)
|
||||
tools.append(query_engine_tool)
|
||||
|
||||
# Add additional tools
|
||||
tools += ToolFactory.from_env()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import os
|
||||
import yaml
|
||||
import json
|
||||
import importlib
|
||||
|
||||
from llama_index.core.tools.tool_spec.base import BaseToolSpec
|
||||
@@ -27,9 +26,8 @@ class ToolFactory:
|
||||
@staticmethod
|
||||
def from_env() -> list[FunctionTool]:
|
||||
tools = []
|
||||
if os.path.exists("config/tools.yaml"):
|
||||
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)
|
||||
with open("config/tools.json", "r") as f:
|
||||
tool_configs = json.load(f)
|
||||
for name, config in tool_configs.items():
|
||||
tools += ToolFactory.create_tool(name, **config)
|
||||
return tools
|
||||
|
||||
@@ -6,13 +6,7 @@ def get_chat_engine():
|
||||
system_prompt = os.getenv("SYSTEM_PROMPT")
|
||||
top_k = os.getenv("TOP_K", 3)
|
||||
|
||||
index = get_index()
|
||||
if index is None:
|
||||
raise Exception(
|
||||
"StorageContext is empty - call 'python app/engine/generate.py' to generate the storage first"
|
||||
)
|
||||
|
||||
return index.as_chat_engine(
|
||||
return get_index().as_chat_engine(
|
||||
similarity_top_k=int(top_k),
|
||||
system_prompt=system_prompt,
|
||||
chat_mode="condense_plus_context",
|
||||
|
||||
@@ -1,44 +1,26 @@
|
||||
import {
|
||||
BaseTool,
|
||||
OpenAI,
|
||||
OpenAIAgent,
|
||||
QueryEngineTool,
|
||||
ToolFactory,
|
||||
} from "llamaindex";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { OpenAI, OpenAIAgent, QueryEngineTool, ToolFactory } from "llamaindex";
|
||||
import { STORAGE_CACHE_DIR } from "./constants.mjs";
|
||||
import { getDataSource } from "./index";
|
||||
import { STORAGE_CACHE_DIR } from "./shared";
|
||||
import config from "./tools_config.json";
|
||||
|
||||
export async function createChatEngine(llm: OpenAI) {
|
||||
let tools: BaseTool[] = [];
|
||||
|
||||
// Add a query engine tool if we have a data source
|
||||
// Delete this code if you don't have a data source
|
||||
const index = await getDataSource(llm);
|
||||
if (index) {
|
||||
tools.push(
|
||||
new QueryEngineTool({
|
||||
queryEngine: index.asQueryEngine(),
|
||||
metadata: {
|
||||
name: "data_query_engine",
|
||||
description: `A query engine for documents in storage folder: ${STORAGE_CACHE_DIR}`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// add tools from config file if it exists
|
||||
const config = JSON.parse(
|
||||
await fs.readFile(path.join("config", "tools.json"), "utf8"),
|
||||
);
|
||||
tools = tools.concat(await ToolFactory.createTools(config));
|
||||
} catch {}
|
||||
|
||||
return new OpenAIAgent({
|
||||
tools,
|
||||
llm,
|
||||
verbose: true,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,6 @@ import { getDataSource } from "./index";
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
if (!index) {
|
||||
throw new Error(
|
||||
`StorageContext is empty - call 'npm run generate' to generate the storage first`,
|
||||
);
|
||||
}
|
||||
const retriever = index.asRetriever();
|
||||
retriever.similarityTopK = 3;
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import os
|
||||
import yaml
|
||||
import json
|
||||
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
|
||||
from app.engine.loaders.db import DBLoaderConfig, get_db_documents
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_configs():
|
||||
with open("config/loaders.yaml") as f:
|
||||
configs = yaml.safe_load(f)
|
||||
with open("config/loaders.json") as f:
|
||||
configs = json.load(f)
|
||||
return configs
|
||||
|
||||
|
||||
@@ -23,17 +22,12 @@ def get_documents():
|
||||
logger.info(
|
||||
f"Loading documents from loader: {loader_type}, config: {loader_config}"
|
||||
)
|
||||
match loader_type:
|
||||
case "file":
|
||||
document = get_file_documents(FileLoaderConfig(**loader_config))
|
||||
case "web":
|
||||
document = get_web_documents(WebLoaderConfig(**loader_config))
|
||||
case "db":
|
||||
document = get_db_documents(
|
||||
configs=[DBLoaderConfig(**cfg) for cfg in loader_config]
|
||||
)
|
||||
case _:
|
||||
raise ValueError(f"Invalid loader type: {loader_type}")
|
||||
documents.extend(document)
|
||||
if loader_type == "file":
|
||||
document = get_file_documents(FileLoaderConfig(**loader_config))
|
||||
documents.extend(document)
|
||||
elif loader_type == "web":
|
||||
for entry in loader_config:
|
||||
document = get_web_documents(WebLoaderConfig(**entry))
|
||||
documents.extend(document)
|
||||
|
||||
return documents
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import os
|
||||
import logging
|
||||
from typing import List
|
||||
from pydantic import BaseModel, validator
|
||||
from llama_index.core.indices.vector_store import VectorStoreIndex
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DBLoaderConfig(BaseModel):
|
||||
uri: str
|
||||
queries: List[str]
|
||||
|
||||
|
||||
def get_db_documents(configs: list[DBLoaderConfig]):
|
||||
from llama_index.readers.database import DatabaseReader
|
||||
|
||||
docs = []
|
||||
for entry in configs:
|
||||
loader = DatabaseReader(uri=entry.uri)
|
||||
for query in entry.queries:
|
||||
logger.info(f"Loading data from database with query: {query}")
|
||||
documents = loader.load_data(query=query)
|
||||
docs.extend(documents)
|
||||
|
||||
return documents
|
||||
@@ -3,34 +3,17 @@ import json
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class CrawlUrl(BaseModel):
|
||||
class WebLoaderConfig(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
|
||||
scraper = WholeSiteReader(
|
||||
prefix=config.prefix,
|
||||
max_depth=config.max_depth,
|
||||
)
|
||||
return scraper.load_data(config.base_url)
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
import os
|
||||
import logging
|
||||
from llama_index.core.storage import StorageContext
|
||||
from llama_index.core.indices import VectorStoreIndex
|
||||
from llama_index.vector_stores.astra_db import AstraDBVectorStore
|
||||
from app.settings import init_settings
|
||||
from app.engine.loaders import get_documents
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
def generate_datasource():
|
||||
logger.info("Creating new index")
|
||||
documents = get_documents()
|
||||
store = AstraDBVectorStore(
|
||||
token=os.environ["ASTRA_DB_APPLICATION_TOKEN"],
|
||||
api_endpoint=os.environ["ASTRA_DB_ENDPOINT"],
|
||||
collection_name=os.environ["ASTRA_DB_COLLECTION"],
|
||||
embedding_dimension=1536,
|
||||
)
|
||||
storage_context = StorageContext.from_defaults(vector_store=store)
|
||||
VectorStoreIndex.from_documents(
|
||||
documents,
|
||||
storage_context=storage_context,
|
||||
show_progress=True, # this will show you a progress bar as the embeddings are created
|
||||
)
|
||||
logger.info(f"Successfully created embeddings in the AstraDB")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_settings()
|
||||
generate_datasource()
|
||||
@@ -1,21 +0,0 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from llama_index.core.indices import VectorStoreIndex
|
||||
from llama_index.vector_stores.astra_db import AstraDBVectorStore
|
||||
|
||||
|
||||
logger = logging.getLogger("uvicorn")
|
||||
|
||||
|
||||
def get_index():
|
||||
logger.info("Connecting to index from AstraDB...")
|
||||
store = AstraDBVectorStore(
|
||||
token=os.environ["ASTRA_DB_APPLICATION_TOKEN"],
|
||||
api_endpoint=os.environ["ASTRA_DB_ENDPOINT"],
|
||||
collection_name=os.environ["ASTRA_DB_COLLECTION"],
|
||||
embedding_dimension=1536,
|
||||
)
|
||||
index = VectorStoreIndex.from_vector_store(store)
|
||||
logger.info("Finished connecting to index from AstraDB.")
|
||||
return index
|
||||
@@ -11,7 +11,10 @@ logger = logging.getLogger("uvicorn")
|
||||
def get_index():
|
||||
# check if storage already exists
|
||||
if not os.path.exists(STORAGE_DIR):
|
||||
return None
|
||||
raise Exception(
|
||||
"StorageContext is empty - call 'python app/engine/generate.py' to generate the storage first"
|
||||
)
|
||||
|
||||
# load the existing index
|
||||
logger.info(f"Loading index from {STORAGE_DIR}...")
|
||||
storage_context = StorageContext.from_defaults(persist_dir=STORAGE_DIR)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import * as dotenv from "dotenv";
|
||||
import {
|
||||
AstraDBVectorStore,
|
||||
VectorStoreIndex,
|
||||
storageContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { getDocuments } from "./loader";
|
||||
import { checkRequiredEnvVars } from "./shared";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
async function loadAndIndex() {
|
||||
// load objects from storage and convert them into LlamaIndex Document objects
|
||||
const documents = await getDocuments();
|
||||
|
||||
// create vector store and a collection
|
||||
const collectionName = process.env.ASTRA_DB_COLLECTION!;
|
||||
const vectorStore = new AstraDBVectorStore();
|
||||
await vectorStore.create(collectionName, {
|
||||
vector: { dimension: 1536, metric: "cosine" },
|
||||
});
|
||||
await vectorStore.connect(collectionName);
|
||||
|
||||
// create index from documents and store them in Astra
|
||||
console.log("Start creating embeddings...");
|
||||
const storageContext = await storageContextFromDefaults({ vectorStore });
|
||||
await VectorStoreIndex.fromDocuments(documents, { storageContext });
|
||||
console.log(
|
||||
"Successfully created embeddings and save to your Astra database.",
|
||||
);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
checkRequiredEnvVars();
|
||||
await loadAndIndex();
|
||||
console.log("Finished generating storage.");
|
||||
})();
|
||||
@@ -1,20 +0,0 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import {
|
||||
AstraDBVectorStore,
|
||||
LLM,
|
||||
VectorStoreIndex,
|
||||
serviceContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { CHUNK_OVERLAP, CHUNK_SIZE, checkRequiredEnvVars } from "./shared";
|
||||
|
||||
export async function getDataSource(llm: LLM) {
|
||||
checkRequiredEnvVars();
|
||||
const serviceContext = serviceContextFromDefaults({
|
||||
llm,
|
||||
chunkSize: CHUNK_SIZE,
|
||||
chunkOverlap: CHUNK_OVERLAP,
|
||||
});
|
||||
const store = new AstraDBVectorStore();
|
||||
await store.connect(process.env.ASTRA_DB_COLLECTION!);
|
||||
return await VectorStoreIndex.fromVectorStore(store, serviceContext);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
export const CHUNK_SIZE = 512;
|
||||
export const CHUNK_OVERLAP = 20;
|
||||
|
||||
const REQUIRED_ENV_VARS = [
|
||||
"ASTRA_DB_APPLICATION_TOKEN",
|
||||
"ASTRA_DB_ENDPOINT",
|
||||
"ASTRA_DB_COLLECTION",
|
||||
];
|
||||
|
||||
export function checkRequiredEnvVars() {
|
||||
const missingEnvVars = REQUIRED_ENV_VARS.filter((envVar) => {
|
||||
return !process.env[envVar];
|
||||
});
|
||||
|
||||
if (missingEnvVars.length > 0) {
|
||||
console.log(
|
||||
`The following environment variables are required but missing: ${missingEnvVars.join(
|
||||
", ",
|
||||
)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Missing environment variables: ${missingEnvVars.join(", ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,8 +5,8 @@ import {
|
||||
VectorStoreIndex,
|
||||
storageContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { getDocuments } from "./loader";
|
||||
import { checkRequiredEnvVars, getMilvusClient } from "./shared";
|
||||
import { getDocuments } from "./loader.mjs";
|
||||
import { checkRequiredEnvVars, getMilvusClient } from "./shared.mjs";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
ContextChatEngine,
|
||||
LLM,
|
||||
MilvusVectorStore,
|
||||
serviceContextFromDefaults,
|
||||
@@ -9,9 +10,9 @@ import {
|
||||
CHUNK_OVERLAP,
|
||||
CHUNK_SIZE,
|
||||
getMilvusClient,
|
||||
} from "./shared";
|
||||
} from "./shared.mjs";
|
||||
|
||||
export async function getDataSource(llm: LLM) {
|
||||
async function getDataSource(llm: LLM) {
|
||||
checkRequiredEnvVars();
|
||||
const serviceContext = serviceContextFromDefaults({
|
||||
llm,
|
||||
@@ -23,3 +24,12 @@ export async function getDataSource(llm: LLM) {
|
||||
|
||||
return await VectorStoreIndex.fromVectorStore(store, serviceContext);
|
||||
}
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever({ similarityTopK: 3 });
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
retriever,
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ export function getMilvusClient() {
|
||||
throw new Error("MILVUS_ADDRESS environment variable is required");
|
||||
}
|
||||
return new MilvusClient({
|
||||
address: process.env.MILVUS_ADDRESS!,
|
||||
address: process.env.MILVUS_ADDRESS,
|
||||
username: process.env.MILVUS_USERNAME,
|
||||
password: process.env.MILVUS_PASSWORD,
|
||||
});
|
||||
+5
-5
@@ -6,14 +6,14 @@ import {
|
||||
storageContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { MongoClient } from "mongodb";
|
||||
import { getDocuments } from "./loader";
|
||||
import { checkRequiredEnvVars } from "./shared";
|
||||
import { getDocuments } from "./loader.mjs";
|
||||
import { checkRequiredEnvVars } from "./shared.mjs";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const mongoUri = process.env.MONGO_URI!;
|
||||
const databaseName = process.env.MONGODB_DATABASE!;
|
||||
const vectorCollectionName = process.env.MONGODB_VECTORS!;
|
||||
const mongoUri = process.env.MONGO_URI;
|
||||
const databaseName = process.env.MONGODB_DATABASE;
|
||||
const vectorCollectionName = process.env.MONGODB_VECTORS;
|
||||
const indexName = process.env.MONGODB_VECTOR_INDEX;
|
||||
|
||||
async function loadAndIndex() {
|
||||
@@ -1,14 +1,15 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import {
|
||||
ContextChatEngine,
|
||||
LLM,
|
||||
MongoDBAtlasVectorSearch,
|
||||
serviceContextFromDefaults,
|
||||
VectorStoreIndex,
|
||||
} from "llamaindex";
|
||||
import { MongoClient } from "mongodb";
|
||||
import { checkRequiredEnvVars, CHUNK_OVERLAP, CHUNK_SIZE } from "./shared";
|
||||
import { checkRequiredEnvVars, CHUNK_OVERLAP, CHUNK_SIZE } from "./shared.mjs";
|
||||
|
||||
export async function getDataSource(llm: LLM) {
|
||||
async function getDataSource(llm: LLM) {
|
||||
checkRequiredEnvVars();
|
||||
const client = new MongoClient(process.env.MONGO_URI!);
|
||||
const serviceContext = serviceContextFromDefaults({
|
||||
@@ -18,10 +19,19 @@ export async function getDataSource(llm: LLM) {
|
||||
});
|
||||
const store = new MongoDBAtlasVectorSearch({
|
||||
mongodbClient: client,
|
||||
dbName: process.env.MONGODB_DATABASE!,
|
||||
collectionName: process.env.MONGODB_VECTORS!,
|
||||
dbName: process.env.MONGODB_DATABASE,
|
||||
collectionName: process.env.MONGODB_VECTORS,
|
||||
indexName: process.env.MONGODB_VECTOR_INDEX,
|
||||
});
|
||||
|
||||
return await VectorStoreIndex.fromVectorStore(store, serviceContext);
|
||||
}
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever({ similarityTopK: 3 });
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
retriever,
|
||||
});
|
||||
}
|
||||
|
||||
+4
-5
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
ServiceContext,
|
||||
serviceContextFromDefaults,
|
||||
storageContextFromDefaults,
|
||||
VectorStoreIndex,
|
||||
@@ -7,20 +6,20 @@ import {
|
||||
|
||||
import * as dotenv from "dotenv";
|
||||
|
||||
import { getDocuments } from "./loader";
|
||||
import { CHUNK_OVERLAP, CHUNK_SIZE, STORAGE_CACHE_DIR } from "./shared";
|
||||
import { CHUNK_OVERLAP, CHUNK_SIZE, STORAGE_CACHE_DIR } from "./constants.mjs";
|
||||
import { getDocuments } from "./loader.mjs";
|
||||
|
||||
// Load environment variables from local .env file
|
||||
dotenv.config();
|
||||
|
||||
async function getRuntime(func: any) {
|
||||
async function getRuntime(func) {
|
||||
const start = Date.now();
|
||||
await func();
|
||||
const end = Date.now();
|
||||
return end - start;
|
||||
}
|
||||
|
||||
async function generateDatasource(serviceContext: ServiceContext) {
|
||||
async function generateDatasource(serviceContext) {
|
||||
console.log(`Generating storage context...`);
|
||||
// Split documents, create embeddings and store them in the storage context
|
||||
const ms = await getRuntime(async () => {
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
storageContextFromDefaults,
|
||||
VectorStoreIndex,
|
||||
} from "llamaindex";
|
||||
import { CHUNK_OVERLAP, CHUNK_SIZE, STORAGE_CACHE_DIR } from "./shared";
|
||||
import { CHUNK_OVERLAP, CHUNK_SIZE, STORAGE_CACHE_DIR } from "./constants.mjs";
|
||||
|
||||
export async function getDataSource(llm: LLM) {
|
||||
const serviceContext = serviceContextFromDefaults({
|
||||
@@ -21,7 +21,9 @@ export async function getDataSource(llm: LLM) {
|
||||
(storageContext.docStore as SimpleDocumentStore).toDict(),
|
||||
).length;
|
||||
if (numberOfDocs === 0) {
|
||||
return null;
|
||||
throw new Error(
|
||||
`StorageContext is empty - call 'npm run generate' to generate the storage first`,
|
||||
);
|
||||
}
|
||||
return await VectorStoreIndex.init({
|
||||
storageContext,
|
||||
|
||||
+2
-2
@@ -5,13 +5,13 @@ import {
|
||||
VectorStoreIndex,
|
||||
storageContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { getDocuments } from "./loader";
|
||||
import { getDocuments } from "./loader.mjs";
|
||||
import {
|
||||
PGVECTOR_COLLECTION,
|
||||
PGVECTOR_SCHEMA,
|
||||
PGVECTOR_TABLE,
|
||||
checkRequiredEnvVars,
|
||||
} from "./shared";
|
||||
} from "./shared.mjs";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import {
|
||||
ContextChatEngine,
|
||||
LLM,
|
||||
PGVectorStore,
|
||||
VectorStoreIndex,
|
||||
@@ -11,9 +12,9 @@ import {
|
||||
PGVECTOR_SCHEMA,
|
||||
PGVECTOR_TABLE,
|
||||
checkRequiredEnvVars,
|
||||
} from "./shared";
|
||||
} from "./shared.mjs";
|
||||
|
||||
export async function getDataSource(llm: LLM) {
|
||||
async function getDataSource(llm: LLM) {
|
||||
checkRequiredEnvVars();
|
||||
const pgvs = new PGVectorStore({
|
||||
connectionString: process.env.PG_CONNECTION_STRING,
|
||||
@@ -27,3 +28,12 @@ export async function getDataSource(llm: LLM) {
|
||||
});
|
||||
return await VectorStoreIndex.fromVectorStore(pgvs, serviceContext);
|
||||
}
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever({ similarityTopK: 3 });
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
retriever,
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import {
|
||||
VectorStoreIndex,
|
||||
storageContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { getDocuments } from "./loader";
|
||||
import { checkRequiredEnvVars } from "./shared";
|
||||
import { getDocuments } from "./loader.mjs";
|
||||
import { checkRequiredEnvVars } from "./shared.mjs";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import {
|
||||
ContextChatEngine,
|
||||
LLM,
|
||||
PineconeVectorStore,
|
||||
VectorStoreIndex,
|
||||
serviceContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { CHUNK_OVERLAP, CHUNK_SIZE, checkRequiredEnvVars } from "./shared";
|
||||
import { CHUNK_OVERLAP, CHUNK_SIZE, checkRequiredEnvVars } from "./shared.mjs";
|
||||
|
||||
export async function getDataSource(llm: LLM) {
|
||||
async function getDataSource(llm: LLM) {
|
||||
checkRequiredEnvVars();
|
||||
const serviceContext = serviceContextFromDefaults({
|
||||
llm,
|
||||
@@ -17,3 +18,12 @@ export async function getDataSource(llm: LLM) {
|
||||
const store = new PineconeVectorStore();
|
||||
return await VectorStoreIndex.fromVectorStore(store, serviceContext);
|
||||
}
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever({ similarityTopK: 5 });
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
retriever,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
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>
|
||||
```
|
||||
|
||||
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):
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
## Using docker
|
||||
|
||||
1. Build an image for FastAPI app:
|
||||
|
||||
```
|
||||
docker build -t <your_backend_image_name> .
|
||||
```
|
||||
|
||||
2. Run a container:
|
||||
|
||||
```
|
||||
docker run --rm -v $(pwd)/.env:/app/.env -p 8000:8000 <your_backend_image_name>
|
||||
```
|
||||
|
||||
## 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!
|
||||
@@ -60,37 +60,18 @@ 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
|
||||
## Using docker
|
||||
|
||||
1. Build an image for the Express API:
|
||||
1. Build an image for Express 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:
|
||||
2. Run a container:
|
||||
|
||||
```
|
||||
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>
|
||||
docker run --rm -v $(pwd)/.env:/app/.env -p 8000:8000 <your_backend_image_name>
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
{
|
||||
"extends": ["eslint:recommended", "prettier"],
|
||||
"rules": {
|
||||
"max-params": ["error", 4],
|
||||
"prefer-const": "error"
|
||||
}
|
||||
"extends": "eslint:recommended"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
{
|
||||
"name": "llama-index-express-streaming",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.mjs",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"format": "prettier --ignore-unknown --cache --check .",
|
||||
"format:write": "prettier --ignore-unknown --write .",
|
||||
"build": "tsup index.ts --format esm --dts",
|
||||
"start": "node dist/index.mjs",
|
||||
"dev": "concurrently \"tsup index.ts --format esm --dts --watch\" \"nodemon -q dist/index.mjs\""
|
||||
"start": "node dist/index.js",
|
||||
"dev": "concurrently \"tsup index.ts --format esm --dts --watch\" \"nodemon -q dist/index.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"ai": "^2.2.25",
|
||||
@@ -24,10 +23,6 @@
|
||||
"eslint": "^8.54.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"tsup": "^8.0.1",
|
||||
"typescript": "^5.3.2",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"ts-node": "^10.9.2"
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: ["prettier-plugin-organize-imports"],
|
||||
};
|
||||
@@ -5,14 +5,6 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
}
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,38 +64,18 @@ The API allows CORS for all origins to simplify development. You can change this
|
||||
ENVIRONMENT=prod python main.py
|
||||
```
|
||||
|
||||
## Using Docker
|
||||
## Using docker
|
||||
|
||||
1. Build an image for the FastAPI app:
|
||||
1. Build an image for 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:
|
||||
2. Run a container:
|
||||
|
||||
```
|
||||
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>
|
||||
docker run --rm -v $(pwd)/.env:/app/.env -p 8000:8000 <your_backend_image_name>
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Any, Optional, Dict, Tuple
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
from llama_index.core.chat_engine.types import (
|
||||
BaseChatEngine,
|
||||
)
|
||||
from llama_index.core.schema import NodeWithScore
|
||||
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()
|
||||
|
||||
@@ -20,40 +18,9 @@ class _Message(BaseModel):
|
||||
class _ChatData(BaseModel):
|
||||
messages: List[_Message]
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What standards for letters exist?",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class _SourceNodes(BaseModel):
|
||||
id: str
|
||||
metadata: Dict[str, Any]
|
||||
score: Optional[float]
|
||||
|
||||
@classmethod
|
||||
def from_source_node(cls, source_node: NodeWithScore):
|
||||
return cls(
|
||||
id=source_node.node.node_id,
|
||||
metadata=source_node.node.metadata,
|
||||
score=source_node.score,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_source_nodes(cls, source_nodes: List[NodeWithScore]):
|
||||
return [cls.from_source_node(node) for node in source_nodes]
|
||||
|
||||
|
||||
class _Result(BaseModel):
|
||||
result: _Message
|
||||
nodes: List[_SourceNodes]
|
||||
|
||||
|
||||
async def parse_chat_data(data: _ChatData) -> Tuple[str, List[ChatMessage]]:
|
||||
@@ -110,6 +77,5 @@ async def chat_request(
|
||||
|
||||
response = await chat_engine.achat(last_message_content, messages)
|
||||
return _Result(
|
||||
result=_Message(role=MessageRole.ASSISTANT, content=response.response),
|
||||
nodes=_SourceNodes.from_source_nodes(response.source_nodes),
|
||||
result=_Message(role=MessageRole.ASSISTANT, content=response.response)
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ import os
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import RedirectResponse
|
||||
from app.api.routers.chat import chat_router
|
||||
from app.settings import init_settings
|
||||
|
||||
@@ -30,12 +29,6 @@ if environment == "dev":
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Redirect to documentation page when accessing base URL
|
||||
@app.get("/")
|
||||
async def redirect_to_docs():
|
||||
return RedirectResponse(url="/docs")
|
||||
|
||||
|
||||
app.include_router(chat_router, prefix="/api/chat")
|
||||
|
||||
|
||||
|
||||
@@ -26,39 +26,18 @@ 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
|
||||
## Using docker
|
||||
|
||||
1. Build an image for the Next.js app:
|
||||
1. Build an image for Next 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:
|
||||
2. Run a container:
|
||||
|
||||
```
|
||||
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>
|
||||
docker run --rm -v $(pwd)/.env:/app/.env -p 3000:3000 <your_app_image_name>
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
{
|
||||
"extends": ["next/core-web-vitals", "prettier"],
|
||||
"rules": {
|
||||
"max-params": ["error", 4],
|
||||
"prefer-const": "error"
|
||||
}
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
"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",
|
||||
@@ -40,10 +38,6 @@
|
||||
"tailwindcss": "^3.3.6",
|
||||
"typescript": "^5.3.2",
|
||||
"@types/react-syntax-highlighter": "^15.5.11",
|
||||
"cross-env": "^7.0.3",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"ts-node": "^10.9.2"
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: ["prettier-plugin-organize-imports"],
|
||||
};
|
||||
@@ -24,10 +24,5 @@
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"],
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
}
|
||||
}
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user