Skip to content
Snippets Groups Projects
Commit 091a5f39 authored by Marcus Schiesser's avatar Marcus Schiesser
Browse files

feat: add streaming express example and align with non-streaming one

parent 19a0ad04
No related branches found
No related tags found
No related merge requests found
Showing
with 188 additions and 28 deletions
......@@ -72,7 +72,7 @@ export const installTemplate = async ({
const routeFile = path.join(
root,
relativeEngineDestPath,
framework === "nextjs" ? "route.ts" : "llm.controller.ts",
framework === "nextjs" ? "route.ts" : "chat.controller.ts",
);
const routeFileContent = await fs.readFile(routeFile, "utf8");
const newContent = routeFileContent.replace(
......
......@@ -14,18 +14,15 @@ Second, run the development server:
npm run dev
```
Then call the express API endpoint `/api/llm` to see the result:
Then call the express API endpoint `/api/chat` to see the result:
```
curl --location 'localhost:3000/api/llm' \
curl --location 'localhost:3000/api/chat' \
--header 'Content-Type: application/json' \
--data '{
"message": "Hello",
"chatHistory": []
}'
--data '{ "messages": [{ "role": "user", "content": "Hello" }] }'
```
You can start editing the API by modifying `src/controllers/llm.controller.ts`. The endpoint auto-updates as you save the file.
You can start editing the API by modifying `src/controllers/chat.controller.ts`. The endpoint auto-updates as you save the file.
## Learn More
......
import express, { Express, Request, Response } from "express";
import llmRouter from "./src/routes/llm.route";
import chatRouter from "./src/routes/chat.route";
const app: Express = express();
const port = 3000;
......@@ -10,7 +10,7 @@ app.get("/", (req: Request, res: Response) => {
res.send("LlamaIndex Express Server");
});
app.use("/api/llm", llmRouter);
app.use("/api/chat", chatRouter);
app.listen(port, () => {
console.log(`⚡️[server]: Server is running at http://localhost:${port}`);
......
{
"name": "llama-index-express",
"version": "1.0.0",
"main": "index.js",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"build": "tsup index.ts --format esm --dts",
"start": "node dist/index.js",
"dev": "concurrently \"tsc --watch\" \"nodemon -q dist/index.js\""
"dev": "concurrently \"tsup index.ts --format esm --dts --watch\" \"nodemon -q dist/index.js\""
},
"dependencies": {
"express": "^4",
......@@ -15,8 +16,9 @@
"@types/express": "^4",
"@types/node": "^20",
"concurrently": "^8",
"eslint": "^8",
"nodemon": "^3",
"typescript": "^5",
"eslint": "^8"
"tsup": "^7",
"typescript": "^5"
}
}
\ No newline at end of file
......@@ -4,16 +4,12 @@ import { createChatEngine } from "../../../../engines/context";
export const chat = async (req: Request, res: Response, next: NextFunction) => {
try {
const {
message,
chatHistory,
}: {
message: string;
chatHistory: ChatMessage[];
} = req.body;
if (!message || !chatHistory) {
const { messages }: { messages: ChatMessage[] } = req.body;
const lastMessage = messages.pop();
if (!messages || !lastMessage || lastMessage.role !== "user") {
return res.status(400).json({
error: "message, chatHistory are required in the request body",
error:
"messages are required in the request body and the last message must be from the user",
});
}
......@@ -23,7 +19,7 @@ export const chat = async (req: Request, res: Response, next: NextFunction) => {
const chatEngine = await createChatEngine(llm);
const response = await chatEngine.chat(message, chatHistory);
const response = await chatEngine.chat(lastMessage.content, messages);
const result: ChatMessage = {
role: "assistant",
content: response.response,
......
import express from "express";
import { chat } from "../controllers/llm.controller";
import { chat } from "../controllers/chat.controller";
const llmRouter = express.Router();
......
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
......
This is a [LlamaIndex](https://www.llamaindex.ai/) project using [Express](https://expressjs.com/) bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama).
## Getting Started
First, install the dependencies:
```
npm install
```
Second, run the development server:
```
npm run dev
```
Then call the express API endpoint `/api/chat` to see the result:
```
curl --location 'localhost:3000/api/chat' \
--header 'Content-Type: application/json' \
--data '{ "messages": [{ "role": "user", "content": "Hello" }] }'
```
You can start editing the API by modifying `src/controllers/chat.controller.ts`. The endpoint auto-updates as you save the file.
## Learn More
To learn more about LlamaIndex, take a look at the following resources:
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex (Python features).
- [LlamaIndexTS Documentation](https://ts.llamaindex.ai) - learn about LlamaIndex (Typescript features).
You can check out [the LlamaIndexTS GitHub repository](https://github.com/run-llama/LlamaIndexTS) - your feedback and contributions are welcome!
{
"extends": "eslint:recommended"
}
\ No newline at end of file
import express, { Express, Request, Response } from "express";
import chatRouter from "./src/routes/chat.route";
const app: Express = express();
const port = 3000;
app.use(express.json());
app.get("/", (req: Request, res: Response) => {
res.send("LlamaIndex Express Server");
});
app.use("/api/chat", chatRouter);
app.listen(port, () => {
console.log(`⚡️[server]: Server is running at http://localhost:${port}`);
});
{
"name": "llama-index-express-streaming",
"version": "1.0.0",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "tsup index.ts --format esm --dts",
"start": "node dist/index.js",
"dev": "concurrently \"tsup index.ts --format esm --dts --watch\" \"nodemon -q dist/index.js\""
},
"dependencies": {
"ai": "^2",
"express": "^4",
"llamaindex": "0.0.31"
},
"devDependencies": {
"@types/express": "^4",
"@types/node": "^20",
"concurrently": "^8",
"eslint": "^8",
"nodemon": "^3",
"tsup": "^7",
"typescript": "^5"
}
}
\ No newline at end of file
import { streamToResponse } from "ai";
import { NextFunction, Request, Response } from "express";
import { ChatMessage, OpenAI } from "llamaindex";
import { createChatEngine } from "../../../../engines/context";
import { LlamaIndexStream } from "./llamaindex-stream";
export const chat = async (req: Request, res: Response, next: NextFunction) => {
try {
const { messages }: { messages: ChatMessage[] } = req.body;
const lastMessage = messages.pop();
if (!messages || !lastMessage || lastMessage.role !== "user") {
return res.status(400).json({
error:
"messages are required in the request body and the last message must be from the user",
});
}
const llm = new OpenAI({
model: "gpt-3.5-turbo",
});
const chatEngine = await createChatEngine(llm);
const response = await chatEngine.chat(lastMessage.content, messages, true);
// Transform the response into a readable stream
const stream = LlamaIndexStream(response);
streamToResponse(stream, res);
} catch (error) {
console.error("[LlamaIndex]", error);
return res.status(500).json({
error: (error as Error).message,
});
}
};
import {
createCallbacksTransformer,
createStreamDataTransformer,
trimStartOfStreamHelper,
type AIStreamCallbacksAndOptions,
} from "ai";
function createParser(res: AsyncGenerator<any>) {
const trimStartOfStream = trimStartOfStreamHelper();
return new ReadableStream<string>({
async pull(controller): Promise<void> {
const { value, done } = await res.next();
if (done) {
controller.close();
return;
}
const text = trimStartOfStream(value ?? "");
if (text) {
controller.enqueue(text);
}
},
});
}
export function LlamaIndexStream(
res: AsyncGenerator<any>,
callbacks?: AIStreamCallbacksAndOptions,
): ReadableStream {
return createParser(res)
.pipeThrough(createCallbacksTransformer(callbacks))
.pipeThrough(
createStreamDataTransformer(callbacks?.experimental_streamData),
);
}
import express from "express";
import { chat } from "../controllers/chat.controller";
const llmRouter = express.Router();
llmRouter.route("/").post(chat);
export default llmRouter;
{
"compilerOptions": {
"target": "es2016",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment