Merge pull request #116 from aakashadesara/added_api_server

Added API Server
This commit is contained in:
David Zhang 2025-03-07 11:33:25 -08:00 committed by GitHub
commit be18bc208d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1186 additions and 2 deletions

992
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
"tsx": "tsx --env-file=.env.local",
"start": "tsx --env-file=.env.local src/run.ts",
"api": "tsx --env-file=.env.local src/api.ts",
"docker": "tsx src/run.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
@ -14,8 +15,11 @@
"description": "",
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.13.0",
"@types/uuid": "^9.0.8",
"prettier": "^3.4.2",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
@ -24,9 +28,12 @@
"@ai-sdk/openai": "^1.1.9",
"@mendable/firecrawl-js": "^1.16.0",
"ai": "^4.1.17",
"cors": "^2.8.5",
"express": "^4.18.3",
"js-tiktoken": "^1.0.17",
"lodash-es": "^4.17.21",
"p-limit": "^6.2.0",
"uuid": "^9.0.1",
"zod": "^3.24.1"
},
"engines": {

144
src/api.ts Normal file
View File

@ -0,0 +1,144 @@
import express, { Request, Response } from 'express';
import cors from 'cors';
import * as fs from 'fs/promises';
import { deepResearch, writeFinalReport, writeFinalAnswer } from './deep-research';
import { generateFeedback } from './feedback';
import { OutputManager } from './output-manager';
const app = express();
const port = process.env.PORT || 3051;
// Middleware
app.use(cors());
app.use(express.json());
// Initialize output manager
const output = new OutputManager();
// Helper function for consistent logging
function log(...args: any[]) {
output.log(...args);
}
// API endpoint to run research
app.post('/api/research', async (req: Request, res: Response) => {
try {
const { query, followUpAnswers = [], depth = 3, breadth = 3 } = req.body;
if (!query) {
return res.status(400).json({ error: 'Query is required' });
}
log(`Creating research plan for: ${query}`);
// Generate follow-up questions
const followUpQuestions = await generateFeedback({
query,
numQuestions: 0
});
// Combine all information for deep research
let combinedQuery = `Initial Query: ${query}`;
if (followUpQuestions.length > 0 && followUpAnswers.length > 0) {
combinedQuery += `\nFollow-up Questions and Answers:\n${
followUpQuestions
.slice(0, followUpAnswers.length)
.map((q: string, i: number) => `Q: ${q}\nA: ${followUpAnswers[i]}`)
.join('\n')
}`;
}
log('\nResearching your topic...');
log('\nStarting research with progress tracking...\n');
// Track progress
let currentProgress = {};
const { learnings, visitedUrls } = await deepResearch({
query: combinedQuery,
breadth,
depth,
onProgress: (progress) => {
output.updateProgress(progress);
currentProgress = progress;
},
});
log(`\n\nLearnings:\n\n${learnings.join('\n')}`);
log(`\n\nVisited URLs (${visitedUrls.length}):\n\n${visitedUrls.join('\n')}`);
log('Writing final report...');
const report = await writeFinalReport({
prompt: combinedQuery,
learnings,
visitedUrls,
});
// Save report to file with timestamp
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const reportFilename = `output-${timestamp}.md`;
// await fs.writeFile(reportFilename, report, 'utf-8');
const answer = await writeFinalAnswer({
prompt: combinedQuery,
learnings,
report,
});
// Save answer to file
const answerFilename = `answer-${timestamp}.md`;
// await fs.writeFile(answerFilename, answer, 'utf-8');
// Return the results
return res.json({
success: true,
report,
answer,
learnings,
visitedUrls,
reportFilename,
answerFilename
});
} catch (error: unknown) {
console.error('Error in research API:', error);
return res.status(500).json({
error: 'An error occurred during research',
message: error instanceof Error ? error.message : String(error)
});
}
});
// API endpoint to get follow-up questions
app.post('/api/questions', async (req: Request, res: Response) => {
try {
const { query, numQuestions = 3 } = req.body;
if (!query) {
return res.status(400).json({ error: 'Query is required' });
}
const followUpQuestions = await generateFeedback({
query,
numQuestions
});
return res.json({
success: true,
questions: followUpQuestions
});
} catch (error: unknown) {
console.error('Error generating questions:', error);
return res.status(500).json({
error: 'An error occurred while generating questions',
message: error instanceof Error ? error.message : String(error)
});
}
});
// Start the server
app.listen(port, () => {
console.log(`Deep Research API running on port ${port}`);
});
export default app;

View File

@ -160,6 +160,37 @@ export async function writeFinalReport({
return res.object.reportMarkdown + urlsSection;
}
export async function writeFinalAnswer({
prompt,
learnings,
report,
}: {
prompt: string;
learnings: string[];
report: string;
}) {
const res = await generateObject({
model: o3MiniModel,
system: systemPrompt(),
prompt: `Given the following prompt from the user, write a final answer on the topic using the learnings from research. Follow the format specified in the prompt. Do not yap or babble or include any other text than the answer besides the format specified in the prompt. Keep the answer as concise as possible - usually it should be just a few words or maximum a sentence. Try to follow the format specified in the prompt (for example, if the prompt is using Latex, the answer should be in Latex. If the prompt gives multiple answer choices, the answer should be one of the choices).
<prompt>${prompt}</prompt>
<report>${report}</report>
<format>
<answer>
</answer>
</format>
`,
schema: z.object({
answer: z.string().describe('The final answer'),
}),
});
return res.object.answer;
}
export async function deepResearch({
query,
breadth,

View File

@ -1,7 +1,8 @@
import * as fs from 'fs/promises';
import * as readline from 'readline';
import { deepResearch, writeFinalReport } from './deep-research';
import { deepResearch, writeFinalAnswer, writeFinalReport } from './deep-research';
import { generateFeedback } from './feedback';
import { OutputManager } from './output-manager';
@ -50,6 +51,7 @@ async function run() {
// Generate follow-up questions
const followUpQuestions = await generateFeedback({
query: initialQuery,
numQuestions: 0
});
log(
@ -101,6 +103,16 @@ ${followUpQuestions.map((q: string, i: number) => `Q: ${q}\nA: ${answers[i]}`).j
console.log(`\n\nFinal Report:\n\n${report}`);
console.log('\nReport has been saved to output.md');
rl.close();
const answer = await writeFinalAnswer({
prompt: combinedQuery,
learnings,
report,
});
console.log(`\n\nFinal Answer:\n\n${answer}`);
await fs.writeFile('answer.md', answer, 'utf-8');
}
run().catch(console.error);