Skip to content

Commit 141e040

Browse files
committed
web and video search tools including Tavily, Exa, and SearXNG integrations for web search, and YouTube video search. Also, implemented PDF processing for converting PDF contents into structured WordPress posts while preserving hyperlinks.
Took 47 seconds
1 parent 1d65047 commit 141e040

18 files changed

+1779
-253
lines changed

lib/agents/fdai/fdaiMetaAnalyzer.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { z } from "zod";
2-
import { anthropic } from "@ai-sdk/anthropic";
32
import { generateObject } from "ai";
43
import Exa from 'exa-js';
4+
import {getModel} from "@/lib/utils/modelUtils";
5+
import { generateMetaAnalysisQuery } from '@/lib/meta-analysis/metaAnalysisQueries';
56

67
const exa = new Exa(process.env.EXA_API_KEY);
78

@@ -56,24 +57,24 @@ const MetaAnalysisReportSchema = z.object({
5657
effectivenessComparison: z.array(z.object({
5758
intervention: z.string().describe('Name of the compared intervention'),
5859
relativeEffectiveness: z.string().describe('Relative effectiveness compared to the main drug'),
59-
dalysAvoided: z.number().optional().describe('Estimated Disability-Adjusted Life Years (DALYs) avoided per patient'),
60-
qalysIncreased: z.number().optional().describe('Estimated Quality-Adjusted Life Years (QALYs) increased per patient'),
61-
numberNeededToHarm: z.number().optional().describe('Number Needed to Harm (NNH) to cause a negative health outcome'),
62-
numberNeededToTreat: z.number().optional().describe('Number Needed to Treat (NNT) to achieve a positive health outcome'),
60+
dalysAvoided: z.union([z.number(), z.string()]).optional().describe('Estimated Disability-Adjusted Life Years (DALYs) avoided per patient'),
61+
qalysIncreased: z.union([z.number(), z.string()]).optional().describe('Estimated Quality-Adjusted Life Years (QALYs) increased per patient'),
62+
numberNeededToHarm: z.union([z.number(), z.string()]).optional().describe('Number Needed to Harm (NNH) to cause a negative health outcome'),
63+
numberNeededToTreat: z.union([z.number(), z.string()]).optional().describe('Number Needed to Treat (NNT) to achieve a positive health outcome'),
6364
})).describe('Comparison of effectiveness with other treatments'),
64-
dalysAvoided: z.number().optional().describe('Estimated Disability-Adjusted Life Years (DALYs) avoided per patient'),
65-
qalysIncreased: z.number().optional().describe('Estimated Quality-Adjusted Life Years (QALYs) increased per patient'),
66-
numberNeededToHarm: z.number().optional().describe('Number Needed to Harm (NNH) to cause a negative health outcome'),
67-
numberOfPatients: z.number().optional().describe('Number of patients globally who would benefit from the drug'),
68-
numberNeededToTreat: z.number().optional().describe('Number Needed to Treat (NNT) to achieve a positive health outcome'),
65+
dalysAvoided: z.union([z.number(), z.string()]).optional().describe('Estimated Disability-Adjusted Life Years (DALYs) avoided per patient'),
66+
qalysIncreased: z.union([z.number(), z.string()]).optional().describe('Estimated Quality-Adjusted Life Years (QALYs) increased per patient'),
67+
numberNeededToHarm: z.union([z.number(), z.string()]).optional().describe('Number Needed to Harm (NNH) to cause a negative health outcome'),
68+
numberOfPatients: z.union([z.number(), z.string()]).optional().describe('Number of patients globally who would benefit from the drug'),
69+
numberNeededToTreat: z.union([z.number(), z.string()]).optional().describe('Number Needed to Treat (NNT) to achieve a positive health outcome'),
6970
//referenceSources: z.array(articleSchema).describe('Sources of information used in compiling this report')
7071
});
7172

7273
export type MetaAnalysisReportType = z.infer<typeof MetaAnalysisReportSchema>;
7374

7475

7576
async function getWebResults(drugName: string, conditionName: string, numResults: number = 10): Promise<Article[]> {
76-
const query = `${drugName} for ${conditionName} meta-analysis safety efficacy "clinical trials" "systematic review"`;
77+
const query = generateMetaAnalysisQuery(drugName, conditionName);
7778
const searchResponse = await exa.searchAndContents(query, {
7879
numResults,
7980
useAutoprompt: false,
@@ -111,8 +112,11 @@ export async function doMetaAnalysis(drugName: string, conditionName: string): P
111112
Web search results:
112113
${webResultsText}`;
113114

115+
//const model = getModel("gemini-1.5-flash")
116+
const model = getModel()
117+
114118
const result = await generateObject({
115-
model: anthropic('claude-3-5-sonnet-20240620'),
119+
model,
116120
schema: MetaAnalysisReportSchema,
117121
prompt,
118122
});

lib/agents/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './task-manager'
2+
export * from './inquire'
3+
export * from './query-suggestor'
4+
export * from './researcher'

lib/agents/inquire.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Copilot } from '@/app/search/components/copilot'
2+
import { createStreamableUI, createStreamableValue } from 'ai/rsc'
3+
import { CoreMessage, streamObject } from 'ai'
4+
import { PartialInquiry, inquirySchema } from '@/lib/schema/inquiry'
5+
import { getModel } from '../utils/index'
6+
7+
export async function inquire(
8+
uiStream: ReturnType<typeof createStreamableUI>,
9+
messages: CoreMessage[]
10+
) {
11+
const objectStream = createStreamableValue<PartialInquiry>()
12+
uiStream.update(<Copilot inquiry={objectStream.value} />)
13+
14+
let finalInquiry: PartialInquiry = {}
15+
await streamObject({
16+
model: getModel(),
17+
system: `As a professional web researcher, your role is to deepen your understanding of the user's input by conducting further inquiries when necessary.
18+
After receiving an initial response from the user, carefully assess whether additional questions are absolutely essential to provide a comprehensive and accurate answer. Only proceed with further inquiries if the available information is insufficient or ambiguous.
19+
20+
When crafting your inquiry, structure it as follows:
21+
{
22+
"question": "A clear, concise question that seeks to clarify the user's intent or gather more specific details.",
23+
"options": [
24+
{"value": "option1", "label": "A predefined option that the user can select"},
25+
{"value": "option2", "label": "Another predefined option"},
26+
...
27+
],
28+
"allowsInput": true/false, // Indicates whether the user can provide a free-form input
29+
"inputLabel": "A label for the free-form input field, if allowed",
30+
"inputPlaceholder": "A placeholder text to guide the user's free-form input"
31+
}
32+
33+
Important: The "value" field in the options must always be in English, regardless of the user's language.
34+
35+
For example:
36+
{
37+
"question": "What specific information are you seeking about Rivian?",
38+
"options": [
39+
{"value": "history", "label": "History"},
40+
{"value": "products", "label": "Products"},
41+
{"value": "investors", "label": "Investors"},
42+
{"value": "partnerships", "label": "Partnerships"},
43+
{"value": "competitors", "label": "Competitors"}
44+
],
45+
"allowsInput": true,
46+
"inputLabel": "If other, please specify",
47+
"inputPlaceholder": "e.g., Specifications"
48+
}
49+
50+
By providing predefined options, you guide the user towards the most relevant aspects of their query, while the free-form input allows them to provide additional context or specific details not covered by the options.
51+
Remember, your goal is to gather the necessary information to deliver a thorough and accurate response.
52+
Please match the language of the response (question, labels, inputLabel, and inputPlaceholder) to the user's language, but keep the "value" field in English.
53+
`,
54+
messages,
55+
schema: inquirySchema
56+
})
57+
.then(async result => {
58+
for await (const obj of result.partialObjectStream) {
59+
if (obj) {
60+
objectStream.update(obj)
61+
finalInquiry = obj
62+
}
63+
}
64+
})
65+
.finally(() => {
66+
objectStream.done()
67+
})
68+
69+
return finalInquiry
70+
}

lib/agents/pdfDownloader.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import fetch from 'node-fetch';
2+
import fs from 'fs/promises';
3+
import path from 'path';
4+
5+
export class PDFDownloader {
6+
private downloadDir: string;
7+
8+
constructor(downloadDir: string) {
9+
this.downloadDir = downloadDir;
10+
}
11+
12+
private async ensureDirectoryExists(): Promise<void> {
13+
try {
14+
await fs.access(this.downloadDir);
15+
} catch {
16+
await fs.mkdir(this.downloadDir, { recursive: true });
17+
}
18+
}
19+
20+
private async downloadPDF(pdfUrl: string): Promise<void> {
21+
const response = await fetch(pdfUrl);
22+
if (!response.ok) {
23+
throw new Error(`Failed to download PDF: ${response.statusText}`);
24+
}
25+
26+
const arrayBuffer = await response.arrayBuffer();
27+
const buffer = Buffer.from(arrayBuffer);
28+
29+
// Extract filename from URL or use a fallback
30+
const urlParts = new URL(pdfUrl);
31+
const fileName = path.basename(urlParts.pathname) || 'document.pdf';
32+
const filePath = path.join(this.downloadDir, fileName);
33+
34+
await fs.writeFile(filePath, buffer);
35+
console.log(`Downloaded: ${fileName}`);
36+
}
37+
38+
public async downloadPDFs(pdfUrls: string[]): Promise<{
39+
downloaded: number;
40+
errors: string[];
41+
}> {
42+
const results = {
43+
downloaded: 0,
44+
errors: [] as string[]
45+
};
46+
47+
try {
48+
await this.ensureDirectoryExists();
49+
50+
console.log(`Starting download of ${pdfUrls.length} PDFs...`);
51+
52+
for (const pdfUrl of pdfUrls) {
53+
try {
54+
await this.downloadPDF(pdfUrl);
55+
results.downloaded++;
56+
} catch (error) {
57+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
58+
results.errors.push(`Error downloading ${pdfUrl}: ${errorMessage}`);
59+
console.error(`Failed to download ${pdfUrl}: ${errorMessage}`);
60+
}
61+
}
62+
} catch (error) {
63+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
64+
results.errors.push(`Error in download process: ${errorMessage}`);
65+
console.error(`Error in download process: ${errorMessage}`);
66+
}
67+
68+
return results;
69+
}
70+
}

0 commit comments

Comments
 (0)