forked from i-am-alice/3rd-devs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.ts
177 lines (145 loc) · 8.47 KB
/
app.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import type { Request, Response } from 'express';
import path from 'path';
import multer from 'multer';
import express from 'express';
import { v4 as uuidv4 } from 'uuid';
import type { IAssistantTools, IDoc } from './types/types';
import { SearchService } from './services/SearchService';
import { DatabaseService } from "./services/DatabaseService";
import { OpenAIService } from "./services/OpenAIService";
import { VectorService } from "./services/VectorService";
import { DocumentService } from './services/DocumentService';
import { TextService } from './services/TextService';
import { FileService } from './services/FileService';
import { WebSearchService } from './services/WebSearch';
import { AssistantService } from './services/AssistantService';
import { unlink } from 'fs/promises';
const fileService = new FileService();
const textService = new TextService();
const openaiService = new OpenAIService();
const vectorService = new VectorService(openaiService);
const searchService = new SearchService(String(process.env.ALGOLIA_APP_ID), String(process.env.ALGOLIA_API_KEY));
const databaseService = new DatabaseService('web/database.db', searchService, vectorService);
const documentService = new DocumentService(openaiService, databaseService, textService);
const webSearchService = new WebSearchService();
const assistantService = new AssistantService(openaiService, fileService, databaseService, webSearchService, documentService, textService);
const app = express();
const port = 3000;
app.use(express.json());
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
export const tools: IAssistantTools[] = [
{ name: 'web_search', description: 'Use only when searching web results or scanning a specific domain (NOT the exact URL / subpage of the page such as /blog). (e.g., "search example.com for apps"). This tool uses Google search to scrape relevant website contents. NEVER use it when the user asks to load specific URLs.' },
{ name: 'file_process', description: 'Use to load and process contents of a file/image from a given URL/path or to process a web search results. Can translate, summarize, synthesize, extract information, or answer questions about the file contents (answering is available for images too). No need to upload files before using this tool.' },
{ name: 'upload', description: 'Use only for creating new files. Allows writing content and uploading it to receive a URL/path to the new file.' },
{ name: 'answer', description: 'MUST be used as the final tool. Use to provide the final answer to the user, communicate results, or inform about limitations, missing data, or inability to complete the task.' },
];
const processors = [
{ name: 'translate', description: 'Use this tool to translate a given URL or path content to the target language. REQUIRED PARAMETERS: url or path, original_language, target_language.' },
{ name: 'summarize', description: 'Use this tool to generate a summary of the given URL or path content. REQUIRED PARAMETERS: \'url or path\'.' },
{ name: 'synthesize', description: 'Use this tool to synthesize the content of the given URL or path content. REQUIRED PARAMETERS: \'url or path\' and \'query\' that describes the synthesis.' },
{ name: 'extract', description: 'Use this tool to extract specific information or content from a given URL or file path. This includes, but is not limited to: links, topics, concepts, article URLs, dates, or any other structured data. REQUIRED PARAMETERS: \'url or path\', \'extraction_type\' (e.g. "links", "recent article URL", "publication dates"), and \'description\' of the extraction.' },
{ name: 'answer', description: 'Use this tool when you need to answer questions based on the content of a given document or image (URL or file path). This tool can interpret and respond to queries about the document\'s subject matter, facts, or visual content. REQUIRED PARAMETERS: \'url or path\', \'question\'.' }
]
const upload = multer({ storage: multer.memoryStorage() });
app.post('/api/chat', async (req, res) => {
let context: IDoc[] = [];
let uploads = '';
let { messages, conversation_uuid } = req.body;
if (messages.some((msg: any) => msg.role === 'system' && msg.content.startsWith('As a copywriter, your job is to provide an ultra-concise name'))) {
return res.json({ choices: [{ message: { role: 'assistant', content: 'AGI is here...' } }] });
}
messages = messages.filter((msg: any) => msg.role !== 'system');
const { plan } = await assistantService.understand(messages, tools);
console.log('Plan of actions:');
console.table(plan.map((step: any) => ({ Tool: step.tool, Query: step.query })));
for (const { tool, query } of plan) {
if (tool === 'web_search') {
const webSearchResults = await assistantService.websearch(query, conversation_uuid);
context = [...context, ...webSearchResults];
}
if (tool === 'file_process') {
console.log(context)
const { process } = await assistantService.process(query, processors, context);
const result = await assistantService.processDocument(process, context, conversation_uuid);
context = [...context, ...result];
}
if (tool === 'upload') {
const file = await assistantService.write(query, context, conversation_uuid);
const result = await assistantService.upload(file);
uploads += `<uploaded_file name="${file.metadata.name}" description="${file.metadata.description}">Path: ${result}</uploaded_file>`
}
}
const answer = await assistantService.answer(plan.at(-1)?.query || '', messages, context, uploads);
// Replace placeholders with the actual content
const answerWithContext = answer.replace(/\[\[([^\]]+)\]\]/g, (match: string, p1: string) => {
const doc = context.find(doc => doc.metadata.uuid === p1);
if (doc) {
const restoredDoc = textService.restorePlaceholders(doc);
return restoredDoc.text;
}
return match;
});
return res.json({ conversation_uuid, choices: [{ message: { role: 'assistant', content: answerWithContext } }] });
});
app.get('/', (req, res) => {
res.send('AGI is here...');
});
app.get('/api/file/:type/:date/:uuid/:filename', async (req: Request, res: Response) => {
try {
const { type, date, uuid, filename } = req.params;
const filePath = path.join(type, date, uuid, filename);
const { data, mimeType } = await fileService.load(filePath);
res.setHeader('Content-Type', mimeType);
res.setHeader('Content-Disposition', `inline; filename="${filename}"`);
res.send(data);
} catch (error: any) {
console.error('File retrieval error:', error);
res.status(404).json({ success: false, error: 'File not found' });
}
});
const storageDir = path.join(process.cwd(), 'storage');
app.post('/api/upload', upload.single('file'), async (req: Request, res: Response) => {
try {
if (!req.file) {
return res.status(400).json({ success: false, error: 'No file uploaded' });
}
const fileContent = req.file.buffer;
const fileName = req.file.originalname;
const fileUUID = uuidv4();
const conversationUUID = req.body.conversation_uuid;
// Save the file temporarily
const tempFilePath = await fileService.writeTempFile(fileContent, fileName);
// Process the file using FileService
try {
const { docs } = await fileService.process(tempFilePath, undefined, conversationUUID);
await Promise.all(docs.map(async (doc) => {
// Add conversation_uuid to the document metadata
doc.metadata.conversation_uuid = conversationUUID;
await databaseService.insertDocument(doc, true);
}));
// Clean up the temporary file
await unlink(tempFilePath).catch((err: any) => {
console.error('Error deleting temp file:', err);
});
res.json({
success: true,
filePath: path.relative(process.cwd(), tempFilePath),
fileName: fileName,
fileUUID: fileUUID,
type: fileService.getFileCategoryFromMimeType(req.file.mimetype),
docs: docs.map(doc => ({
text: doc.text,
metadata: doc.metadata
}))
});
} catch (processError: any) {
console.error('Error processing file:', processError);
res.status(500).json({ success: false, error: `Error processing file: ${processError.message}` });
}
} catch (error: any) {
console.error('Upload error:', error);
res.status(500).json({ success: false, error: error.message });
}
});