Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b41f36c8eb | |||
| 54a910fc86 | |||
| ca76e0c221 | |||
| 90d259c7b0 | |||
| 54ca85d482 | |||
| 65ef0be90c | |||
| 57106affdf | |||
| d613bbd358 | |||
| 36f0af5a5d | |||
| 79d7076121 | |||
| 526b3e74bf | |||
| d03dc21e8a | |||
| c31dfa4957 | |||
| 1fe02a3067 | |||
| 31cf3cde45 | |||
| 11f0c2cab1 |
@@ -20,3 +20,43 @@ const serviceContext = serviceContextFromDefaults({ llm: openaiLLM });
|
||||
|
||||
- [OpenAI](../../api/classes/OpenAI.md)
|
||||
- [ServiceContext](../../api/interfaces/ServiceContext.md)
|
||||
|
||||
## Usage
|
||||
|
||||
The LLM object tracks API consumption across all your code. This is done through the `llm.usage` property.
|
||||
|
||||
_Note: Usage is not supported for stream calls_
|
||||
|
||||
```javascript
|
||||
import { OpenAI } from "llamaindex";
|
||||
|
||||
// Create a new instance of OpenAI with the specified model and temperature
|
||||
const llm = new OpenAI({ model: "gpt-3.5-turbo", temperature: 0 });
|
||||
|
||||
async function ask() {
|
||||
// Send a chat request to the OpenAI API
|
||||
const response = await llm.chat([
|
||||
{
|
||||
role: "system",
|
||||
content: "You are a helpful llama.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: "Where do llama live?",
|
||||
},
|
||||
]);
|
||||
|
||||
// Log the response from the API
|
||||
console.log(response);
|
||||
/**
|
||||
The response includes a message from the assistant and usage information.
|
||||
The usage information includes the number of prompt tokens, completion tokens, and total tokens used.
|
||||
*/
|
||||
|
||||
// Log the usage information from the LLM object
|
||||
console.log(llm.usage);
|
||||
/**
|
||||
The usage object includes the number of prompt tokens, completion tokens, the cost, and the compute seconds.
|
||||
*/
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# mongodb-llamaindexts
|
||||
|
||||
## 0.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3bab231]
|
||||
- llamaindex@0.0.37
|
||||
|
||||
## 0.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- llamaindex@0.0.36
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"version": "0.0.3",
|
||||
"private": true,
|
||||
"name": "mongodb-llamaindexts",
|
||||
"dependencies": {
|
||||
"llamaindex": "workspace:*",
|
||||
"dotenv": "^16.3.1",
|
||||
"mongodb": "^6.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.18.6",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint ."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { program } from "commander";
|
||||
import { AudioTranscriptReader, TranscribeParams } from "llamaindex";
|
||||
import { stdin as input, stdout as output } from "node:process";
|
||||
// readline/promises is still experimental so not in @types/node yet
|
||||
// @ts-ignore
|
||||
import readline from "node:readline/promises";
|
||||
import { VectorStoreIndex } from "../../packages/core/src/indices";
|
||||
|
||||
program
|
||||
.option(
|
||||
"-a, --audio-url [string]",
|
||||
"URL or path of the audio file to transcribe",
|
||||
)
|
||||
.option("-i, --transcript-id [string]", "ID of the AssemblyAI transcript")
|
||||
.action(async (options) => {
|
||||
if (!process.env.ASSEMBLYAI_API_KEY) {
|
||||
console.log("No ASSEMBLYAI_API_KEY found in environment variables.");
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new AudioTranscriptReader();
|
||||
let params: TranscribeParams | string;
|
||||
if (options.audioUrl) {
|
||||
params = {
|
||||
audio: options.audioUrl,
|
||||
};
|
||||
} else if (options.transcriptId) {
|
||||
params = options.transcriptId;
|
||||
} else {
|
||||
console.log(
|
||||
"You must provide either an --audio-url or a --transcript-id",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const documents = await reader.loadData(params);
|
||||
console.log(documents);
|
||||
|
||||
// Split text and create embeddings. Store them in a VectorStoreIndex
|
||||
const index = await VectorStoreIndex.fromDocuments(documents);
|
||||
|
||||
// Create query engine
|
||||
const queryEngine = index.asQueryEngine();
|
||||
|
||||
const rl = readline.createInterface({ input, output });
|
||||
while (true) {
|
||||
const query = await rl.question("Ask a question: ");
|
||||
|
||||
if (!query) {
|
||||
break;
|
||||
}
|
||||
|
||||
const response = await queryEngine.query(query);
|
||||
|
||||
console.log(response.toString());
|
||||
}
|
||||
});
|
||||
|
||||
program.parse();
|
||||
@@ -0,0 +1,59 @@
|
||||
import { program } from "commander";
|
||||
import { AudioTranscriptReader, TranscribeParams } from "llamaindex";
|
||||
import { stdin as input, stdout as output } from "node:process";
|
||||
// readline/promises is still experimental so not in @types/node yet
|
||||
// @ts-ignore
|
||||
import readline from "node:readline/promises";
|
||||
import { VectorStoreIndex } from "../../packages/core/src/indices";
|
||||
|
||||
program
|
||||
.option(
|
||||
"-a, --audio-url [string]",
|
||||
"URL or path of the audio file to transcribe",
|
||||
)
|
||||
.option("-i, --transcript-id [string]", "ID of the AssemblyAI transcript")
|
||||
.action(async (options) => {
|
||||
if (!process.env.ASSEMBLYAI_API_KEY) {
|
||||
console.log("No ASSEMBLYAI_API_KEY found in environment variables.");
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new AudioTranscriptReader();
|
||||
let params: TranscribeParams | string;
|
||||
if (options.audioUrl) {
|
||||
params = {
|
||||
audio: options.audioUrl,
|
||||
};
|
||||
} else if (options.transcriptId) {
|
||||
params = options.transcriptId;
|
||||
} else {
|
||||
console.log(
|
||||
"You must provide either an --audio-url or a --transcript-id",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const documents = await reader.loadData(params);
|
||||
console.log(documents);
|
||||
|
||||
// Split text and create embeddings. Store them in a VectorStoreIndex
|
||||
const index = await VectorStoreIndex.fromDocuments(documents);
|
||||
|
||||
// Create query engine
|
||||
const queryEngine = index.asQueryEngine();
|
||||
|
||||
const rl = readline.createInterface({ input, output });
|
||||
while (true) {
|
||||
const query = await rl.question("Ask a question: ");
|
||||
|
||||
if (!query) {
|
||||
break;
|
||||
}
|
||||
|
||||
const response = await queryEngine.query(query);
|
||||
|
||||
console.log(response.toString());
|
||||
}
|
||||
});
|
||||
|
||||
program.parse();
|
||||
@@ -6,7 +6,7 @@ import { MongoClient } from "mongodb";
|
||||
// Load environment variables from local .env file
|
||||
dotenv.config();
|
||||
|
||||
const jsonFile = "tinytweets.json";
|
||||
const jsonFile = "./data/tinytweets.json";
|
||||
const mongoUri = process.env.MONGODB_URI!;
|
||||
const databaseName = process.env.MONGODB_DATABASE!;
|
||||
const collectionName = process.env.MONGODB_COLLECTION!;
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
### Prepare Environment
|
||||
|
||||
Make sure to run `pnpm install` and set your OpenAI environment variable before running these examples.
|
||||
|
||||
```
|
||||
pnpm install
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
```
|
||||
Read and follow the instructions in the [README.md](../README.md) file located one directory up to make sure your JS/TS dependencies are set up. The commands listed below are also run from that parent directory.
|
||||
|
||||
### Sign up for MongoDB Atlas
|
||||
|
||||
@@ -21,7 +16,7 @@ The signup process will walk you through the process of creating your cluster an
|
||||
|
||||
### Set up environment variables
|
||||
|
||||
Copy the connection string (make sure you include your password) and put it into a file called `.env` in the root of this repo. It should look like this:
|
||||
Copy the connection string (make sure you include your password) and put it into a file called `.env` in the parent folder of this directory. It should look like this:
|
||||
|
||||
```
|
||||
MONGODB_URI=mongodb+srv://seldo:xxxxxxxxxxx@llamaindexdemocluster.xfrdhpz.mongodb.net/?retryWrites=true&w=majority
|
||||
@@ -39,7 +34,7 @@ MONGODB_COLLECTION=tiny_tweets_collection
|
||||
You are now ready to import our ready-made data set into Mongo. This is the file `tinytweets.json`, a selection of approximately 1000 tweets from @seldo on Twitter in mid-2019. With your environment set up you can do this by running
|
||||
|
||||
```
|
||||
pnpm ts-node 1_import.ts
|
||||
npx ts-node mongodb/1_import.ts
|
||||
```
|
||||
|
||||
If you don't want to use tweets, you can replace `json_file` with any other array of JSON objects, but you will need to modify some code later to make sure the correct field gets indexed. There is no LlamaIndex-specific code here; you can load your data into Mongo any way you want to.
|
||||
@@ -64,7 +59,7 @@ MONGODB_VECTOR_INDEX=tiny_tweets_vector_index
|
||||
If the data you're indexing is the tweets we gave you, you're ready to go:
|
||||
|
||||
```bash
|
||||
pnpm ts-node 2_load_and_index.ts
|
||||
npx ts-node mongodb/2_load_and_index.ts
|
||||
```
|
||||
|
||||
> Note: this script is running a couple of minutes and currently doesn't show any progress.
|
||||
@@ -119,7 +114,7 @@ Now you're ready to query your data!
|
||||
You can do this by running
|
||||
|
||||
```bash
|
||||
pnpm ts-node 3_query.ts
|
||||
npx ts-node mongodb/3_query.ts
|
||||
```
|
||||
|
||||
This sets up a connection to Atlas just like `2_load_and_index.ts` did, then it creates a [query engine](https://docs.llamaindex.ai/en/stable/understanding/querying/querying.html#getting-started) and runs a query against it.
|
||||
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 360 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 230 KiB |
|
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 278 KiB |
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 211 KiB |
@@ -6,7 +6,9 @@
|
||||
"@notionhq/client": "^2.2.13",
|
||||
"@pinecone-database/pinecone": "^1.1.2",
|
||||
"commander": "^11.1.0",
|
||||
"llamaindex": "latest"
|
||||
"llamaindex": "latest",
|
||||
"dotenv": "^16.3.1",
|
||||
"mongodb": "^6.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.18.6",
|
||||
@@ -15,4 +17,4 @@
|
||||
"scripts": {
|
||||
"lint": "eslint ."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
"@anthropic-ai/sdk": "^0.9.1",
|
||||
"@notionhq/client": "^2.2.13",
|
||||
"@xenova/transformers": "^2.8.0",
|
||||
"assemblyai": "^3.1.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"js-tiktoken": "^1.0.8",
|
||||
"lodash": "^4.17.21",
|
||||
|
||||
@@ -19,6 +19,7 @@ export * from "./constants";
|
||||
export * from "./embeddings";
|
||||
export * from "./indices";
|
||||
export * from "./llm/LLM";
|
||||
export * from "./readers/AssemblyAI";
|
||||
export * from "./readers/CSVReader";
|
||||
export * from "./readers/HTMLReader";
|
||||
export * from "./readers/MarkdownReader";
|
||||
|
||||
@@ -45,6 +45,8 @@ export interface ChatResponse {
|
||||
message: ChatMessage;
|
||||
raw?: Record<string, any>;
|
||||
delta?: string;
|
||||
metrics?: any;
|
||||
usage?: Usage;
|
||||
}
|
||||
|
||||
// NOTE in case we need CompletionResponse to diverge from ChatResponse in the future
|
||||
@@ -98,19 +100,44 @@ export interface LLM {
|
||||
* Calculates the number of tokens needed for the given chat messages
|
||||
*/
|
||||
tokens(messages: ChatMessage[]): number;
|
||||
|
||||
/**
|
||||
* Returns the usage information of the LLM
|
||||
*/
|
||||
usage: Usage;
|
||||
}
|
||||
|
||||
export const GPT4_MODELS = {
|
||||
"gpt-4": { contextWindow: 8192 },
|
||||
"gpt-4-32k": { contextWindow: 32768 },
|
||||
"gpt-4-1106-preview": { contextWindow: 128000 },
|
||||
"gpt-4-vision-preview": { contextWindow: 8192 },
|
||||
"gpt-4": { contextWindow: 8192, promptCost: 0.03, completionCost: 0.06 },
|
||||
"gpt-4-32k": { contextWindow: 32768, promptCost: 0.06, completionCost: 0.12 },
|
||||
"gpt-4-1106-preview": {
|
||||
contextWindow: 128000,
|
||||
promptCost: 0.01,
|
||||
completionCost: 0.03,
|
||||
},
|
||||
"gpt-4-vision-preview": {
|
||||
contextWindow: 8192,
|
||||
promptCost: 0.01,
|
||||
completionCost: 0.03,
|
||||
},
|
||||
};
|
||||
|
||||
export const GPT35_MODELS = {
|
||||
"gpt-3.5-turbo": { contextWindow: 4096 },
|
||||
"gpt-3.5-turbo-16k": { contextWindow: 16384 },
|
||||
"gpt-3.5-turbo-1106": { contextWindow: 16384 },
|
||||
"gpt-3.5-turbo": {
|
||||
contextWindow: 4096,
|
||||
promptCost: 0.001,
|
||||
completionCost: 0.002,
|
||||
},
|
||||
"gpt-3.5-turbo-16k": {
|
||||
contextWindow: 16384,
|
||||
promptCost: 0.001,
|
||||
completionCost: 0.002,
|
||||
},
|
||||
"gpt-3.5-turbo-1106": {
|
||||
contextWindow: 16384,
|
||||
promptCost: 0.001,
|
||||
completionCost: 0.002,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -121,6 +148,19 @@ export const ALL_AVAILABLE_OPENAI_MODELS = {
|
||||
...GPT35_MODELS,
|
||||
};
|
||||
|
||||
export class Usage {
|
||||
promptTokens: number;
|
||||
completionTokens: number;
|
||||
computeSeconds: number;
|
||||
cost: number;
|
||||
constructor() {
|
||||
this.promptTokens = 0;
|
||||
this.completionTokens = 0;
|
||||
this.cost = 0;
|
||||
this.computeSeconds = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenAI LLM implementation
|
||||
*/
|
||||
@@ -149,6 +189,7 @@ export class OpenAI implements LLM {
|
||||
|
||||
callbackManager?: CallbackManager;
|
||||
|
||||
usage: Usage;
|
||||
constructor(
|
||||
init?: Partial<OpenAI> & {
|
||||
azure?: AzureOpenAIConfig;
|
||||
@@ -164,6 +205,8 @@ export class OpenAI implements LLM {
|
||||
this.additionalChatOptions = init?.additionalChatOptions;
|
||||
this.additionalSessionOptions = init?.additionalSessionOptions;
|
||||
|
||||
this.usage = new Usage();
|
||||
|
||||
if (init?.azure || shouldUseAzure()) {
|
||||
const azureConfig = getAzureConfigFromEnv({
|
||||
...init?.azure,
|
||||
@@ -278,8 +321,21 @@ export class OpenAI implements LLM {
|
||||
});
|
||||
|
||||
const content = response.choices[0].message?.content ?? "";
|
||||
|
||||
// Update usage
|
||||
this.usage.promptTokens += response.usage?.prompt_tokens || 0;
|
||||
this.usage.completionTokens += response.usage?.completion_tokens || 0;
|
||||
this.usage.cost +=
|
||||
((response.usage?.prompt_tokens || 0) *
|
||||
ALL_AVAILABLE_OPENAI_MODELS[this.model].promptCost) /
|
||||
1000 +
|
||||
((response.usage?.completion_tokens || 0) *
|
||||
ALL_AVAILABLE_OPENAI_MODELS[this.model].completionCost) /
|
||||
1000;
|
||||
|
||||
return {
|
||||
message: { content, role: response.choices[0].message.role },
|
||||
usage: response.usage,
|
||||
} as R;
|
||||
}
|
||||
|
||||
@@ -373,23 +429,27 @@ export const ALL_AVAILABLE_LLAMADEUCE_MODELS = {
|
||||
replicateApi:
|
||||
"replicate/llama70b-v2-chat:e951f18578850b652510200860fc4ea62b3b16fac280f83ff32282f87bbd2e48",
|
||||
//^ Previous 70b model. This is also actually 4 bit, although not exllama.
|
||||
costPerSecond: 0.0014,
|
||||
},
|
||||
"Llama-2-70b-chat-4bit": {
|
||||
contextWindow: 4096,
|
||||
replicateApi:
|
||||
"meta/llama-2-70b-chat:02e509c789964a7ea8736978a43525956ef40397be9033abf9fd2badfe68c9e3",
|
||||
//^ Model is based off of exllama 4bit.
|
||||
costPerSecond: 0.0014,
|
||||
},
|
||||
"Llama-2-13b-chat-old": {
|
||||
contextWindow: 4096,
|
||||
replicateApi:
|
||||
"a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5",
|
||||
costPerSecond: 0.000725,
|
||||
},
|
||||
//^ Last known good 13b non-quantized model. In future versions they add the SYS and INST tags themselves
|
||||
"Llama-2-13b-chat-4bit": {
|
||||
contextWindow: 4096,
|
||||
replicateApi:
|
||||
"meta/llama-2-13b-chat:f4e2de70d66816a838a89eeeb621910adffb0dd0baba3976c96980970978018d",
|
||||
costPerSecond: 0.000725,
|
||||
},
|
||||
"Llama-2-7b-chat-old": {
|
||||
contextWindow: 4096,
|
||||
@@ -399,11 +459,13 @@ export const ALL_AVAILABLE_LLAMADEUCE_MODELS = {
|
||||
// tags themselves
|
||||
// https://github.com/replicate/cog-llama-template/commit/fa5ce83912cf82fc2b9c01a4e9dc9bff6f2ef137
|
||||
// Problem is that they fix the max_new_tokens issue in the same commit. :-(
|
||||
costPerSecond: 0.000725,
|
||||
},
|
||||
"Llama-2-7b-chat-4bit": {
|
||||
contextWindow: 4096,
|
||||
replicateApi:
|
||||
"meta/llama-2-7b-chat:13c3cdee13ee059ab779f0291d29054dab00a47dad8261375654de5540165fb0",
|
||||
costPerSecond: 0.000725,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -430,7 +492,7 @@ export class LlamaDeuce implements LLM {
|
||||
maxTokens?: number;
|
||||
replicateSession: ReplicateSession;
|
||||
hasStreaming: boolean;
|
||||
|
||||
usage: Usage;
|
||||
constructor(init?: Partial<LlamaDeuce>) {
|
||||
this.model = init?.model ?? "Llama-2-70b-chat-4bit";
|
||||
this.chatStrategy =
|
||||
@@ -445,6 +507,7 @@ export class LlamaDeuce implements LLM {
|
||||
ALL_AVAILABLE_LLAMADEUCE_MODELS[this.model].contextWindow; // For Replicate, the default is 500 tokens which is too low.
|
||||
this.replicateSession = init?.replicateSession ?? new ReplicateSession();
|
||||
this.hasStreaming = init?.hasStreaming ?? false;
|
||||
this.usage = new Usage();
|
||||
}
|
||||
|
||||
tokens(messages: ChatMessage[]): number {
|
||||
@@ -616,16 +679,23 @@ If a question does not make any sense, or is not factually coherent, explain why
|
||||
//TODO: Add streaming for this
|
||||
|
||||
//Non-streaming
|
||||
const response = await this.replicateSession.replicate.run(
|
||||
const response = (await this.replicateSession.replicate.run(
|
||||
api,
|
||||
replicateOptions,
|
||||
);
|
||||
)) as any;
|
||||
|
||||
this.usage.computeSeconds += response.metrics?.predict_time;
|
||||
this.usage.cost +=
|
||||
response.metrics?.predict_time *
|
||||
ALL_AVAILABLE_LLAMADEUCE_MODELS[this.model].costPerSecond;
|
||||
|
||||
return {
|
||||
message: {
|
||||
content: (response as Array<string>).join("").trimStart(),
|
||||
//^ We need to do this because Replicate returns a list of strings (for streaming functionality which is not exposed by the run function)
|
||||
role: "assistant",
|
||||
},
|
||||
metrics: response.metrics,
|
||||
} as R;
|
||||
}
|
||||
|
||||
@@ -639,8 +709,12 @@ If a question does not make any sense, or is not factually coherent, explain why
|
||||
|
||||
export const ALL_AVAILABLE_ANTHROPIC_MODELS = {
|
||||
// both models have 100k context window, see https://docs.anthropic.com/claude/reference/selecting-a-model
|
||||
"claude-2": { contextWindow: 200000 },
|
||||
"claude-instant-1": { contextWindow: 100000 },
|
||||
"claude-2": { contextWindow: 200000, promptCost: 8.0, completionCost: 24.0 },
|
||||
"claude-instant-1": {
|
||||
contextWindow: 100000,
|
||||
promptCost: 0.8, // for 1 Million tokens
|
||||
completionCost: 2.4, // for 1 Million tokens
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -664,6 +738,7 @@ export class Anthropic implements LLM {
|
||||
|
||||
callbackManager?: CallbackManager;
|
||||
|
||||
usage: Usage;
|
||||
constructor(init?: Partial<Anthropic>) {
|
||||
this.model = init?.model ?? "claude-2";
|
||||
this.temperature = init?.temperature ?? 0.1;
|
||||
@@ -681,6 +756,7 @@ export class Anthropic implements LLM {
|
||||
timeout: this.timeout,
|
||||
});
|
||||
|
||||
this.usage = new Usage();
|
||||
this.callbackManager = init?.callbackManager;
|
||||
}
|
||||
|
||||
@@ -809,6 +885,7 @@ export class Portkey implements LLM {
|
||||
session: PortkeySession;
|
||||
callbackManager?: CallbackManager;
|
||||
|
||||
usage: Usage;
|
||||
constructor(init?: Partial<Portkey>) {
|
||||
this.apiKey = init?.apiKey;
|
||||
this.baseURL = init?.baseURL;
|
||||
@@ -821,6 +898,8 @@ export class Portkey implements LLM {
|
||||
mode: this.mode,
|
||||
});
|
||||
this.callbackManager = init?.callbackManager;
|
||||
|
||||
this.usage = new Usage();
|
||||
}
|
||||
|
||||
tokens(messages: ChatMessage[]): number {
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import {
|
||||
AssemblyAI,
|
||||
BaseServiceParams,
|
||||
SubtitleFormat,
|
||||
TranscribeParams,
|
||||
TranscriptParagraph,
|
||||
TranscriptSentence,
|
||||
} from "assemblyai";
|
||||
import { Document } from "../Node";
|
||||
import { BaseReader } from "./base";
|
||||
|
||||
type AssemblyAIOptions = Partial<BaseServiceParams>;
|
||||
|
||||
/**
|
||||
* Base class for AssemblyAI Readers.
|
||||
*/
|
||||
abstract class AssemblyAIReader implements BaseReader {
|
||||
protected client: AssemblyAI;
|
||||
|
||||
/**
|
||||
* Creates a new AssemblyAI Reader.
|
||||
* @param assemblyAIOptions The options to configure the AssemblyAI Reader.
|
||||
* Configure the `assemblyAIOptions.apiKey` with your AssemblyAI API key, or configure it as the `ASSEMBLYAI_API_KEY` environment variable.
|
||||
*/
|
||||
constructor(assemblyAIOptions?: AssemblyAIOptions) {
|
||||
let options = assemblyAIOptions;
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (!options.apiKey) {
|
||||
options.apiKey = process.env.ASSEMBLYAI_API_KEY;
|
||||
}
|
||||
if (!options.apiKey) {
|
||||
throw new Error(
|
||||
"No AssemblyAI API key provided. Pass an `apiKey` option, or configure the `ASSEMBLYAI_API_KEY` environment variable.",
|
||||
);
|
||||
}
|
||||
|
||||
this.client = new AssemblyAI(options as BaseServiceParams);
|
||||
}
|
||||
|
||||
abstract loadData(...args: any[]): Promise<Document[]>;
|
||||
|
||||
protected async transcribeOrGetTranscript(params: TranscribeParams | string) {
|
||||
if (typeof params === "string") {
|
||||
return await this.client.transcripts.get(params);
|
||||
} else {
|
||||
return await this.client.transcripts.transcribe(params);
|
||||
}
|
||||
}
|
||||
|
||||
protected async getTranscriptId(params: TranscribeParams | string) {
|
||||
if (typeof params === "string") {
|
||||
return params;
|
||||
} else {
|
||||
return (await this.client.transcripts.transcribe(params)).id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transcribe audio and read the transcript as a document using AssemblyAI.
|
||||
*/
|
||||
class AudioTranscriptReader extends AssemblyAIReader {
|
||||
/**
|
||||
* Transcribe audio or get a transcript and load the transcript as a document using AssemblyAI.
|
||||
* @param params Parameters to transcribe an audio file or get an existing transcript.
|
||||
* @returns A promise that resolves to a single document containing the transcript text.
|
||||
*/
|
||||
async loadData(params: TranscribeParams | string): Promise<Document[]> {
|
||||
const transcript = await this.transcribeOrGetTranscript(params);
|
||||
return [new Document({ text: transcript.text || undefined })];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transcribe audio and return a document for each paragraph.
|
||||
*/
|
||||
class AudioTranscriptParagraphsReader extends AssemblyAIReader {
|
||||
/**
|
||||
* Transcribe audio or get a transcript, and returns a document for each paragraph.
|
||||
* @param params The parameters to transcribe audio or get an existing transcript.
|
||||
* @returns A promise that resolves to an array of documents, each containing a paragraph of the transcript.
|
||||
*/
|
||||
async loadData(params: TranscribeParams | string): Promise<Document[]> {
|
||||
let transcriptId = await this.getTranscriptId(params);
|
||||
const paragraphsResponse =
|
||||
await this.client.transcripts.paragraphs(transcriptId);
|
||||
return paragraphsResponse.paragraphs.map(
|
||||
(p: TranscriptParagraph) => new Document({ text: p.text }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transcribe audio and return a document for each sentence.
|
||||
*/
|
||||
class AudioTranscriptSentencesReader extends AssemblyAIReader {
|
||||
/**
|
||||
* Transcribe audio or get a transcript, and returns a document for each sentence.
|
||||
* @param params The parameters to transcribe audio or get an existing transcript.
|
||||
* @returns A promise that resolves to an array of documents, each containing a sentence of the transcript.
|
||||
*/
|
||||
async loadData(params: TranscribeParams | string): Promise<Document[]> {
|
||||
let transcriptId = await this.getTranscriptId(params);
|
||||
const sentencesResponse =
|
||||
await this.client.transcripts.sentences(transcriptId);
|
||||
return sentencesResponse.sentences.map(
|
||||
(p: TranscriptSentence) => new Document({ text: p.text }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transcribe audio a transcript and read subtitles for the transcript as `srt` or `vtt` format.
|
||||
*/
|
||||
class AudioSubtitlesReader extends AssemblyAIReader {
|
||||
/**
|
||||
* Transcribe audio or get a transcript and reads subtitles for the transcript as `srt` or `vtt` format.
|
||||
* @param params The parameters to transcribe audio or get an existing transcript.
|
||||
* @param subtitleFormat The format of the subtitles, either `srt` or `vtt`.
|
||||
* @returns A promise that resolves a document containing the subtitles as the page content.
|
||||
*/
|
||||
async loadData(
|
||||
params: TranscribeParams | string,
|
||||
subtitleFormat: SubtitleFormat = "srt",
|
||||
): Promise<Document[]> {
|
||||
let transcriptId = await this.getTranscriptId(params);
|
||||
const subtitles = await this.client.transcripts.subtitles(
|
||||
transcriptId,
|
||||
subtitleFormat,
|
||||
);
|
||||
return [new Document({ text: subtitles })];
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
AudioSubtitlesReader,
|
||||
AudioTranscriptParagraphsReader,
|
||||
AudioTranscriptReader,
|
||||
AudioTranscriptSentencesReader,
|
||||
};
|
||||
export type { AssemblyAIOptions, SubtitleFormat, TranscribeParams };
|
||||
@@ -31,6 +31,7 @@ export async function createApp({
|
||||
frontend,
|
||||
openAIKey,
|
||||
model,
|
||||
communityProjectPath,
|
||||
}: InstallAppArgs): Promise<void> {
|
||||
const root = path.resolve(appPath);
|
||||
|
||||
@@ -69,6 +70,7 @@ export async function createApp({
|
||||
eslint,
|
||||
openAIKey,
|
||||
model,
|
||||
communityProjectPath,
|
||||
};
|
||||
|
||||
if (frontend) {
|
||||
@@ -106,7 +108,7 @@ export async function createApp({
|
||||
console.log(
|
||||
`Now have a look at the ${terminalLink(
|
||||
"README.md",
|
||||
`file://${appName}/README.md`,
|
||||
`file://${root}/README.md`,
|
||||
)} and learn how to get started.`,
|
||||
);
|
||||
console.log();
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const COMMUNITY_OWNER = "run-llama";
|
||||
export const COMMUNITY_REPO = "create_llama_projects";
|
||||
@@ -0,0 +1,63 @@
|
||||
import { createWriteStream, promises } from "fs";
|
||||
import got from "got";
|
||||
import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
import { Stream } from "stream";
|
||||
import tar from "tar";
|
||||
import { promisify } from "util";
|
||||
import { makeDir } from "./make-dir";
|
||||
|
||||
export type RepoInfo = {
|
||||
username: string;
|
||||
name: string;
|
||||
branch: string;
|
||||
filePath: string;
|
||||
};
|
||||
|
||||
const pipeline = promisify(Stream.pipeline);
|
||||
|
||||
async function downloadTar(url: string) {
|
||||
const tempFile = join(tmpdir(), `next.js-cna-example.temp-${Date.now()}`);
|
||||
await pipeline(got.stream(url), createWriteStream(tempFile));
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
export async function downloadAndExtractRepo(
|
||||
root: string,
|
||||
{ username, name, branch, filePath }: RepoInfo,
|
||||
) {
|
||||
await makeDir(root);
|
||||
|
||||
const tempFile = await downloadTar(
|
||||
`https://codeload.github.com/${username}/${name}/tar.gz/${branch}`,
|
||||
);
|
||||
|
||||
await tar.x({
|
||||
file: tempFile,
|
||||
cwd: root,
|
||||
strip: filePath ? filePath.split("/").length + 1 : 1,
|
||||
filter: (p) =>
|
||||
p.startsWith(
|
||||
`${name}-${branch.replace(/\//g, "-")}${
|
||||
filePath ? `/${filePath}/` : "/"
|
||||
}`,
|
||||
),
|
||||
});
|
||||
|
||||
await promises.unlink(tempFile);
|
||||
}
|
||||
|
||||
export async function getRepoRootFolders(
|
||||
owner: string,
|
||||
repo: string,
|
||||
): Promise<string[]> {
|
||||
const url = `https://api.github.com/repos/${owner}/${repo}/contents`;
|
||||
|
||||
const response = await got(url, {
|
||||
responseType: "json",
|
||||
});
|
||||
|
||||
const data = response.body as any[];
|
||||
const folders = data.filter((item) => item.type === "dir");
|
||||
return folders.map((item) => item.name);
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import ciInfo from "ci-info";
|
||||
import Commander from "commander";
|
||||
import Conf from "conf";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { blue, bold, cyan, green, red, yellow } from "picocolors";
|
||||
import { bold, cyan, green, red, yellow } from "picocolors";
|
||||
import prompts from "prompts";
|
||||
import checkForUpdate from "update-check";
|
||||
import { InstallAppArgs, createApp } from "./create-app";
|
||||
import { createApp } from "./create-app";
|
||||
import { getPkgManager } from "./helpers/get-pkg-manager";
|
||||
import { isFolderEmpty } from "./helpers/is-folder-empty";
|
||||
import { validateNpmName } from "./helpers/validate-pkg";
|
||||
import packageJson from "./package.json";
|
||||
import { QuestionArgs, askQuestions, onPromptState } from "./questions";
|
||||
|
||||
let projectPath: string = "";
|
||||
|
||||
@@ -21,16 +21,6 @@ const handleSigTerm = () => process.exit(0);
|
||||
process.on("SIGINT", handleSigTerm);
|
||||
process.on("SIGTERM", handleSigTerm);
|
||||
|
||||
const onPromptState = (state: any) => {
|
||||
if (state.aborted) {
|
||||
// If we don't re-enable the terminal cursor before exiting
|
||||
// the program, the cursor will remain hidden
|
||||
process.stdout.write("\x1B[?25h");
|
||||
process.stdout.write("\n");
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
const program = new Commander.Command(packageJson.name)
|
||||
.version(packageJson.version)
|
||||
.arguments("<project-directory>")
|
||||
@@ -155,220 +145,8 @@ async function run(): Promise<void> {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// TODO: use Args also for program
|
||||
type Args = Omit<InstallAppArgs, "appPath" | "packageManager">;
|
||||
|
||||
const preferences = (conf.get("preferences") || {}) as Args;
|
||||
|
||||
const defaults: Args = {
|
||||
template: "streaming",
|
||||
framework: "nextjs",
|
||||
engine: "simple",
|
||||
ui: "html",
|
||||
eslint: true,
|
||||
frontend: false,
|
||||
openAIKey: "",
|
||||
model: "gpt-3.5-turbo",
|
||||
};
|
||||
const getPrefOrDefault = (field: keyof Args) =>
|
||||
preferences[field] ?? defaults[field];
|
||||
|
||||
const handlers = {
|
||||
onCancel: () => {
|
||||
console.error("Exiting.");
|
||||
process.exit(1);
|
||||
},
|
||||
};
|
||||
|
||||
if (!program.framework) {
|
||||
if (ciInfo.isCI) {
|
||||
program.framework = getPrefOrDefault("framework");
|
||||
} else {
|
||||
const { framework } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "framework",
|
||||
message: "Which framework would you like to use?",
|
||||
choices: [
|
||||
{ title: "NextJS", value: "nextjs" },
|
||||
{ title: "Express", value: "express" },
|
||||
{ title: "FastAPI (Python)", value: "fastapi" },
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.framework = framework;
|
||||
preferences.framework = framework;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.framework === "nextjs") {
|
||||
program.template = "streaming";
|
||||
}
|
||||
if (!program.template) {
|
||||
if (ciInfo.isCI) {
|
||||
program.template = getPrefOrDefault("template");
|
||||
} else {
|
||||
const { template } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "template",
|
||||
message: "Which template would you like to use?",
|
||||
choices: [
|
||||
{ title: "Chat without streaming", value: "simple" },
|
||||
{ title: "Chat with streaming", value: "streaming" },
|
||||
],
|
||||
initial: 1,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.template = template;
|
||||
preferences.template = template;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.framework === "express" || program.framework === "fastapi") {
|
||||
// if a backend-only framework is selected, ask whether we should create a frontend
|
||||
if (!program.frontend) {
|
||||
if (ciInfo.isCI) {
|
||||
program.frontend = getPrefOrDefault("frontend");
|
||||
} else {
|
||||
const styledNextJS = blue("NextJS");
|
||||
const styledBackend = green(
|
||||
program.framework === "express"
|
||||
? "Express "
|
||||
: program.framework === "fastapi"
|
||||
? "FastAPI (Python) "
|
||||
: "",
|
||||
);
|
||||
const { frontend } = await prompts({
|
||||
onState: onPromptState,
|
||||
type: "toggle",
|
||||
name: "frontend",
|
||||
message: `Would you like to generate a ${styledNextJS} frontend for your ${styledBackend}backend?`,
|
||||
initial: getPrefOrDefault("frontend"),
|
||||
active: "Yes",
|
||||
inactive: "No",
|
||||
});
|
||||
program.frontend = Boolean(frontend);
|
||||
preferences.frontend = Boolean(frontend);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (program.framework === "nextjs") {
|
||||
if (!program.model) {
|
||||
if (ciInfo.isCI) {
|
||||
program.model = getPrefOrDefault("model");
|
||||
} else {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "model",
|
||||
message: "Which model would you like to use?",
|
||||
choices: [
|
||||
{ title: "gpt-3.5-turbo", value: "gpt-3.5-turbo" },
|
||||
{ title: "gpt-4", value: "gpt-4" },
|
||||
{ title: "gpt-4-1106-preview", value: "gpt-4-1106-preview" },
|
||||
{ title: "gpt-4-vision-preview", value: "gpt-4-vision-preview" },
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.model = model;
|
||||
preferences.model = model;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (program.framework === "express" || program.framework === "nextjs") {
|
||||
if (!program.engine) {
|
||||
if (ciInfo.isCI) {
|
||||
program.engine = getPrefOrDefault("engine");
|
||||
} else {
|
||||
const { engine } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "engine",
|
||||
message: "Which chat engine would you like to use?",
|
||||
choices: [
|
||||
{ title: "ContextChatEngine", value: "context" },
|
||||
{
|
||||
title: "SimpleChatEngine (no data, just chat)",
|
||||
value: "simple",
|
||||
},
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.engine = engine;
|
||||
preferences.engine = engine;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.openAIKey) {
|
||||
const { key } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
name: "key",
|
||||
message: "Please provide your OpenAI API key (leave blank to skip):",
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.openAIKey = key;
|
||||
preferences.openAIKey = key;
|
||||
}
|
||||
|
||||
if (
|
||||
program.framework !== "fastapi" &&
|
||||
!process.argv.includes("--eslint") &&
|
||||
!process.argv.includes("--no-eslint")
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
const preferences = (conf.get("preferences") || {}) as QuestionArgs;
|
||||
await askQuestions(program as unknown as QuestionArgs, preferences);
|
||||
|
||||
await createApp({
|
||||
template: program.template,
|
||||
@@ -381,6 +159,7 @@ async function run(): Promise<void> {
|
||||
frontend: program.frontend,
|
||||
openAIKey: program.openAIKey,
|
||||
model: program.model,
|
||||
communityProjectPath: program.communityProjectPath,
|
||||
});
|
||||
conf.set("preferences", preferences);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
import ciInfo from "ci-info";
|
||||
import { blue, green } from "picocolors";
|
||||
import prompts from "prompts";
|
||||
import { InstallAppArgs } from "./create-app";
|
||||
import { COMMUNITY_OWNER, COMMUNITY_REPO } from "./helpers/constant";
|
||||
import { getRepoRootFolders } from "./helpers/repo";
|
||||
|
||||
export type QuestionArgs = Omit<InstallAppArgs, "appPath" | "packageManager">;
|
||||
|
||||
const defaults: QuestionArgs = {
|
||||
template: "streaming",
|
||||
framework: "nextjs",
|
||||
engine: "simple",
|
||||
ui: "html",
|
||||
eslint: true,
|
||||
frontend: false,
|
||||
openAIKey: "",
|
||||
model: "gpt-3.5-turbo",
|
||||
communityProjectPath: "",
|
||||
};
|
||||
|
||||
const handlers = {
|
||||
onCancel: () => {
|
||||
console.error("Exiting.");
|
||||
process.exit(1);
|
||||
},
|
||||
};
|
||||
|
||||
export const onPromptState = (state: any) => {
|
||||
if (state.aborted) {
|
||||
// If we don't re-enable the terminal cursor before exiting
|
||||
// the program, the cursor will remain hidden
|
||||
process.stdout.write("\x1B[?25h");
|
||||
process.stdout.write("\n");
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
export const askQuestions = async (
|
||||
program: QuestionArgs,
|
||||
preferences: QuestionArgs,
|
||||
) => {
|
||||
const getPrefOrDefault = <K extends keyof QuestionArgs>(
|
||||
field: K,
|
||||
): QuestionArgs[K] => preferences[field] ?? defaults[field];
|
||||
|
||||
if (!program.template) {
|
||||
if (ciInfo.isCI) {
|
||||
program.template = getPrefOrDefault("template");
|
||||
} else {
|
||||
const styledRepo = blue(
|
||||
`https://github.com/${COMMUNITY_OWNER}/${COMMUNITY_REPO}`,
|
||||
);
|
||||
const { template } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "template",
|
||||
message: "Which template would you like to use?",
|
||||
choices: [
|
||||
{ title: "Chat without streaming", value: "simple" },
|
||||
{ title: "Chat with streaming", value: "streaming" },
|
||||
{
|
||||
title: `Community template from ${styledRepo}`,
|
||||
value: "community",
|
||||
},
|
||||
],
|
||||
initial: 1,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.template = template;
|
||||
preferences.template = template;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.template === "community") {
|
||||
const rootFolderNames = await getRepoRootFolders(
|
||||
COMMUNITY_OWNER,
|
||||
COMMUNITY_REPO,
|
||||
);
|
||||
const { communityProjectPath } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "communityProjectPath",
|
||||
message: "Select community template",
|
||||
choices: rootFolderNames.map((name) => ({
|
||||
title: name,
|
||||
value: name,
|
||||
})),
|
||||
initial: 0,
|
||||
},
|
||||
{
|
||||
onCancel: () => {
|
||||
console.error("Exiting.");
|
||||
process.exit(1);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
program.communityProjectPath = communityProjectPath;
|
||||
preferences.communityProjectPath = communityProjectPath;
|
||||
return; // early return - no further questions needed for community projects
|
||||
}
|
||||
|
||||
if (!program.framework) {
|
||||
if (ciInfo.isCI) {
|
||||
program.framework = getPrefOrDefault("framework");
|
||||
} else {
|
||||
const choices = [
|
||||
{ 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(
|
||||
{
|
||||
type: "select",
|
||||
name: "framework",
|
||||
message: "Which framework would you like to use?",
|
||||
choices,
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.framework = framework;
|
||||
preferences.framework = framework;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.framework === "express" || program.framework === "fastapi") {
|
||||
// if a backend-only framework is selected, ask whether we should create a frontend
|
||||
if (!program.frontend) {
|
||||
if (ciInfo.isCI) {
|
||||
program.frontend = getPrefOrDefault("frontend");
|
||||
} else {
|
||||
const styledNextJS = blue("NextJS");
|
||||
const styledBackend = green(
|
||||
program.framework === "express"
|
||||
? "Express "
|
||||
: program.framework === "fastapi"
|
||||
? "FastAPI (Python) "
|
||||
: "",
|
||||
);
|
||||
const { frontend } = await prompts({
|
||||
onState: onPromptState,
|
||||
type: "toggle",
|
||||
name: "frontend",
|
||||
message: `Would you like to generate a ${styledNextJS} frontend for your ${styledBackend}backend?`,
|
||||
initial: getPrefOrDefault("frontend"),
|
||||
active: "Yes",
|
||||
inactive: "No",
|
||||
});
|
||||
program.frontend = Boolean(frontend);
|
||||
preferences.frontend = Boolean(frontend);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (program.framework === "nextjs") {
|
||||
if (!program.model) {
|
||||
if (ciInfo.isCI) {
|
||||
program.model = getPrefOrDefault("model");
|
||||
} else {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "model",
|
||||
message: "Which model would you like to use?",
|
||||
choices: [
|
||||
{ title: "gpt-3.5-turbo", value: "gpt-3.5-turbo" },
|
||||
{ title: "gpt-4", value: "gpt-4" },
|
||||
{ title: "gpt-4-1106-preview", value: "gpt-4-1106-preview" },
|
||||
{
|
||||
title: "gpt-4-vision-preview",
|
||||
value: "gpt-4-vision-preview",
|
||||
},
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.model = model;
|
||||
preferences.model = model;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (program.framework === "express" || program.framework === "nextjs") {
|
||||
if (!program.engine) {
|
||||
if (ciInfo.isCI) {
|
||||
program.engine = getPrefOrDefault("engine");
|
||||
} else {
|
||||
const { engine } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "engine",
|
||||
message: "Which chat engine would you like to use?",
|
||||
choices: [
|
||||
{ title: "ContextChatEngine", value: "context" },
|
||||
{
|
||||
title: "SimpleChatEngine (no data, just chat)",
|
||||
value: "simple",
|
||||
},
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.engine = engine;
|
||||
preferences.engine = engine;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.openAIKey) {
|
||||
const { key } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
name: "key",
|
||||
message: "Please provide your OpenAI API key (leave blank to skip):",
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.openAIKey = key;
|
||||
preferences.openAIKey = key;
|
||||
}
|
||||
|
||||
if (
|
||||
program.framework !== "fastapi" &&
|
||||
!process.argv.includes("--eslint") &&
|
||||
!process.argv.includes("--no-eslint")
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -7,7 +7,9 @@ import path from "path";
|
||||
import { bold, cyan } from "picocolors";
|
||||
import { version } from "../../core/package.json";
|
||||
|
||||
import { COMMUNITY_OWNER, COMMUNITY_REPO } from "../helpers/constant";
|
||||
import { PackageManager } from "../helpers/get-pkg-manager";
|
||||
import { downloadAndExtractRepo } from "../helpers/repo";
|
||||
import {
|
||||
InstallTemplateArgs,
|
||||
TemplateEngine,
|
||||
@@ -306,10 +308,29 @@ const installPythonTemplate = async ({
|
||||
);
|
||||
};
|
||||
|
||||
const installCommunityProject = async ({
|
||||
root,
|
||||
communityProjectPath,
|
||||
}: Pick<InstallTemplateArgs, "root" | "communityProjectPath">) => {
|
||||
console.log("\nInstalling community project:", communityProjectPath!);
|
||||
await downloadAndExtractRepo(root, {
|
||||
username: COMMUNITY_OWNER,
|
||||
name: COMMUNITY_REPO,
|
||||
branch: "main",
|
||||
filePath: communityProjectPath!,
|
||||
});
|
||||
};
|
||||
|
||||
export const installTemplate = async (
|
||||
props: InstallTemplateArgs & { backend: boolean },
|
||||
) => {
|
||||
process.chdir(props.root);
|
||||
|
||||
if (props.template === "community" && props.communityProjectPath) {
|
||||
await installCommunityProject(props);
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.framework === "fastapi") {
|
||||
await installPythonTemplate(props);
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PackageManager } from "../helpers/get-pkg-manager";
|
||||
|
||||
export type TemplateType = "simple" | "streaming";
|
||||
export type TemplateType = "simple" | "streaming" | "community";
|
||||
export type TemplateFramework = "nextjs" | "express" | "fastapi";
|
||||
export type TemplateEngine = "simple" | "context";
|
||||
export type TemplateUI = "html" | "shadcn";
|
||||
@@ -19,4 +19,5 @@ export interface InstallTemplateArgs {
|
||||
openAIKey?: string;
|
||||
forBackend?: string;
|
||||
model: string;
|
||||
communityProjectPath?: string;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ module.exports = {
|
||||
"OPENAI_API_KEY",
|
||||
"REPLICATE_API_TOKEN",
|
||||
"ANTHROPIC_API_KEY",
|
||||
"ASSEMBLYAI_API_KEY",
|
||||
|
||||
"AZURE_OPENAI_KEY",
|
||||
"AZURE_OPENAI_ENDPOINT",
|
||||
|
||||
@@ -104,25 +104,6 @@ importers:
|
||||
specifier: ^4.9.5
|
||||
version: 4.9.5
|
||||
|
||||
apps/mongodb:
|
||||
dependencies:
|
||||
dotenv:
|
||||
specifier: ^16.3.1
|
||||
version: 16.3.1
|
||||
llamaindex:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/core
|
||||
mongodb:
|
||||
specifier: ^6.2.0
|
||||
version: 6.2.0
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^18.18.6
|
||||
version: 18.18.8
|
||||
ts-node:
|
||||
specifier: ^10.9.1
|
||||
version: 10.9.1(@types/node@18.18.8)(typescript@5.3.2)
|
||||
|
||||
examples:
|
||||
dependencies:
|
||||
'@notionhq/client':
|
||||
@@ -134,9 +115,15 @@ importers:
|
||||
commander:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.0
|
||||
dotenv:
|
||||
specifier: ^16.3.1
|
||||
version: 16.3.1
|
||||
llamaindex:
|
||||
specifier: latest
|
||||
version: link:../packages/core
|
||||
mongodb:
|
||||
specifier: ^6.2.0
|
||||
version: 6.3.0
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^18.18.6
|
||||
@@ -156,6 +143,9 @@ importers:
|
||||
'@xenova/transformers':
|
||||
specifier: ^2.8.0
|
||||
version: 2.8.0
|
||||
assemblyai:
|
||||
specifier: ^3.1.3
|
||||
version: 3.1.3
|
||||
crypto-js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
@@ -4461,12 +4451,6 @@ packages:
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
/@types/node@18.18.8:
|
||||
resolution: {integrity: sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==}
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
dev: true
|
||||
|
||||
/@types/node@20.9.0:
|
||||
resolution: {integrity: sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==}
|
||||
dependencies:
|
||||
@@ -4641,13 +4625,6 @@ packages:
|
||||
'@types/webidl-conversions': 7.0.2
|
||||
dev: false
|
||||
|
||||
/@types/whatwg-url@8.2.2:
|
||||
resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==}
|
||||
dependencies:
|
||||
'@types/node': 18.18.12
|
||||
'@types/webidl-conversions': 7.0.2
|
||||
dev: false
|
||||
|
||||
/@types/ws@8.5.6:
|
||||
resolution: {integrity: sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==}
|
||||
dependencies:
|
||||
@@ -5175,6 +5152,15 @@ packages:
|
||||
safer-buffer: 2.1.2
|
||||
dev: true
|
||||
|
||||
/assemblyai@3.1.3:
|
||||
resolution: {integrity: sha512-MOVibx4jcKk48lUKoLQWCAnWzm8cBL99GnQ7Af/2XTkGBVUCefocjIO5kJWqRdwLAdoD1D0csR+l4ll62i9vyQ==}
|
||||
dependencies:
|
||||
ws: 8.14.2
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/assert@2.1.0:
|
||||
resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==}
|
||||
dependencies:
|
||||
@@ -10940,13 +10926,6 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/mongodb-connection-string-url@2.6.0:
|
||||
resolution: {integrity: sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==}
|
||||
dependencies:
|
||||
'@types/whatwg-url': 8.2.2
|
||||
whatwg-url: 11.0.0
|
||||
dev: false
|
||||
|
||||
/mongodb-connection-string-url@3.0.0:
|
||||
resolution: {integrity: sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==}
|
||||
dependencies:
|
||||
@@ -10954,38 +10933,6 @@ packages:
|
||||
whatwg-url: 13.0.0
|
||||
dev: false
|
||||
|
||||
/mongodb@6.2.0:
|
||||
resolution: {integrity: sha512-d7OSuGjGWDZ5usZPqfvb36laQ9CPhnWkAGHT61x5P95p/8nMVeH8asloMwW6GcYFeB0Vj4CB/1wOTDG2RA9BFA==}
|
||||
engines: {node: '>=16.20.1'}
|
||||
peerDependencies:
|
||||
'@aws-sdk/credential-providers': ^3.188.0
|
||||
'@mongodb-js/zstd': ^1.1.0
|
||||
gcp-metadata: ^5.2.0
|
||||
kerberos: ^2.0.1
|
||||
mongodb-client-encryption: '>=6.0.0 <7'
|
||||
snappy: ^7.2.2
|
||||
socks: ^2.7.1
|
||||
peerDependenciesMeta:
|
||||
'@aws-sdk/credential-providers':
|
||||
optional: true
|
||||
'@mongodb-js/zstd':
|
||||
optional: true
|
||||
gcp-metadata:
|
||||
optional: true
|
||||
kerberos:
|
||||
optional: true
|
||||
mongodb-client-encryption:
|
||||
optional: true
|
||||
snappy:
|
||||
optional: true
|
||||
socks:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@mongodb-js/saslprep': 1.1.1
|
||||
bson: 6.2.0
|
||||
mongodb-connection-string-url: 2.6.0
|
||||
dev: false
|
||||
|
||||
/mongodb@6.3.0:
|
||||
resolution: {integrity: sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==}
|
||||
engines: {node: '>=16.20.1'}
|
||||
@@ -14615,13 +14562,6 @@ packages:
|
||||
punycode: 2.3.1
|
||||
dev: true
|
||||
|
||||
/tr46@3.0.0:
|
||||
resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
dev: false
|
||||
|
||||
/tr46@4.1.1:
|
||||
resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -14721,37 +14661,6 @@ packages:
|
||||
yn: 3.1.1
|
||||
dev: true
|
||||
|
||||
/ts-node@10.9.1(@types/node@18.18.8)(typescript@5.3.2):
|
||||
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@swc/core': '>=1.2.50'
|
||||
'@swc/wasm': '>=1.2.50'
|
||||
'@types/node': '*'
|
||||
typescript: '>=2.7'
|
||||
peerDependenciesMeta:
|
||||
'@swc/core':
|
||||
optional: true
|
||||
'@swc/wasm':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.9
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.4
|
||||
'@types/node': 18.18.8
|
||||
acorn: 8.11.2
|
||||
acorn-walk: 8.3.0
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 5.3.2
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
dev: true
|
||||
|
||||
/tsconfig-paths@3.14.2:
|
||||
resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==}
|
||||
dependencies:
|
||||
@@ -15710,14 +15619,6 @@ packages:
|
||||
engines: {node: '>=0.8.0'}
|
||||
dev: false
|
||||
|
||||
/whatwg-url@11.0.0:
|
||||
resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
tr46: 3.0.0
|
||||
webidl-conversions: 7.0.0
|
||||
dev: false
|
||||
|
||||
/whatwg-url@13.0.0:
|
||||
resolution: {integrity: sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||