mirror of
https://github.com/jina-ai/node-DeepResearch.git
synced 2026-03-22 07:29:35 +08:00
feat: improve token tracking and centralize config
- Add centralized config.ts for API keys and model settings - Track token usage from all Gemini model calls - Update tools to return token usage alongside results - Fix const reassignment in agent.ts Co-Authored-By: Han Xiao <han.xiao@jina.ai>
This commit is contained in:
58
src/agent.ts
58
src/agent.ts
@@ -1,26 +1,13 @@
|
|||||||
import {GoogleGenerativeAI, SchemaType} from "@google/generative-ai";
|
import { GoogleGenerativeAI, SchemaType } from "@google/generative-ai";
|
||||||
import dotenv from 'dotenv';
|
import { readUrl } from "./tools/read";
|
||||||
import {ProxyAgent, setGlobalDispatcher} from "undici";
|
|
||||||
import {readUrl} from "./tools/read";
|
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import {SafeSearchType, search} from "duck-duck-scrape";
|
import { SafeSearchType, search } from "duck-duck-scrape";
|
||||||
import {rewriteQuery} from "./tools/query-rewriter";
|
import { rewriteQuery } from "./tools/query-rewriter";
|
||||||
import {dedupQueries} from "./tools/dedup";
|
import { dedupQueries } from "./tools/dedup";
|
||||||
import {evaluateAnswer} from "./tools/evaluator";
|
import { evaluateAnswer } from "./tools/evaluator";
|
||||||
import {StepData} from "./tools/getURLIndex";
|
import { StepData } from "./tools/getURLIndex";
|
||||||
import {analyzeSteps} from "./tools/error-analyzer";
|
import { analyzeSteps } from "./tools/error-analyzer";
|
||||||
|
import { GEMINI_API_KEY, JINA_API_KEY, MODEL_NAME } from "./config";
|
||||||
// Proxy setup remains the same
|
|
||||||
if (process.env.https_proxy) {
|
|
||||||
try {
|
|
||||||
const proxyUrl = new URL(process.env.https_proxy).toString();
|
|
||||||
const dispatcher = new ProxyAgent({uri: proxyUrl});
|
|
||||||
setGlobalDispatcher(dispatcher);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set proxy:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
async function sleep(ms: number) {
|
async function sleep(ms: number) {
|
||||||
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
||||||
@@ -326,7 +313,7 @@ async function getResponse(question: string, tokenBudget: number = 1000000) {
|
|||||||
console.log('Prompt len:', prompt.length)
|
console.log('Prompt len:', prompt.length)
|
||||||
|
|
||||||
const model = genAI.getGenerativeModel({
|
const model = genAI.getGenerativeModel({
|
||||||
model: modelName,
|
model: MODEL_NAME,
|
||||||
generationConfig: {
|
generationConfig: {
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
responseMimeType: "application/json",
|
responseMimeType: "application/json",
|
||||||
@@ -351,7 +338,8 @@ async function getResponse(question: string, tokenBudget: number = 1000000) {
|
|||||||
...action,
|
...action,
|
||||||
});
|
});
|
||||||
|
|
||||||
const evaluation = await evaluateAnswer(currentQuestion, action.answer);
|
const { response: evaluation, tokens: evalTokens } = await evaluateAnswer(currentQuestion, action.answer);
|
||||||
|
totalTokens += evalTokens;
|
||||||
|
|
||||||
if (currentQuestion === question) {
|
if (currentQuestion === question) {
|
||||||
if (badAttempts >= 3) {
|
if (badAttempts >= 3) {
|
||||||
@@ -420,7 +408,8 @@ The evaluator thinks your answer is bad because:
|
|||||||
${evaluation.reasoning}
|
${evaluation.reasoning}
|
||||||
`);
|
`);
|
||||||
// store the bad context and reset the diary context
|
// store the bad context and reset the diary context
|
||||||
const errorAnalysis = await analyzeSteps(diaryContext);
|
const { response: errorAnalysis, tokens: analyzeTokens } = await analyzeSteps(diaryContext);
|
||||||
|
totalTokens += analyzeTokens;
|
||||||
badContext.push(errorAnalysis);
|
badContext.push(errorAnalysis);
|
||||||
badAttempts++;
|
badAttempts++;
|
||||||
diaryContext = [];
|
diaryContext = [];
|
||||||
@@ -477,11 +466,14 @@ But then you realized you have asked them before. You decided to to think out of
|
|||||||
}
|
}
|
||||||
else if (action.action === 'search' && action.searchQuery) {
|
else if (action.action === 'search' && action.searchQuery) {
|
||||||
// rewrite queries
|
// rewrite queries
|
||||||
let keywordsQueries = await rewriteQuery(action.searchQuery);
|
let { keywords: keywordsQueries, tokens: rewriteTokens } = await rewriteQuery(action.searchQuery);
|
||||||
|
totalTokens += rewriteTokens;
|
||||||
const oldKeywords = keywordsQueries;
|
const oldKeywords = keywordsQueries;
|
||||||
// avoid exisitng searched queries
|
// avoid exisitng searched queries
|
||||||
if (allKeywords.length) {
|
if (allKeywords.length) {
|
||||||
keywordsQueries = await dedupQueries(keywordsQueries, allKeywords)
|
const { unique_queries: dedupedQueries, tokens: dedupTokens } = await dedupQueries(keywordsQueries, allKeywords);
|
||||||
|
totalTokens += dedupTokens;
|
||||||
|
keywordsQueries = dedupedQueries;
|
||||||
}
|
}
|
||||||
if (keywordsQueries.length > 0) {
|
if (keywordsQueries.length > 0) {
|
||||||
const searchResults = [];
|
const searchResults = [];
|
||||||
@@ -533,7 +525,7 @@ You decided to think out of the box or cut from a completely different angle.
|
|||||||
else if (action.action === 'visit' && action.URLTargets?.length) {
|
else if (action.action === 'visit' && action.URLTargets?.length) {
|
||||||
const urlResults = await Promise.all(
|
const urlResults = await Promise.all(
|
||||||
action.URLTargets.map(async (url: string) => {
|
action.URLTargets.map(async (url: string) => {
|
||||||
const response = await readUrl(url, jinaToken);
|
const response = await readUrl(url, JINA_API_KEY);
|
||||||
allKnowledge.push({
|
allKnowledge.push({
|
||||||
question: `What is in ${response.data.url}?`,
|
question: `What is in ${response.data.url}?`,
|
||||||
answer: removeAllLineBreaks(response.data.content)});
|
answer: removeAllLineBreaks(response.data.content)});
|
||||||
@@ -574,13 +566,7 @@ async function storeContext(prompt: string, memory: any[][], step: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiKey = process.env.GEMINI_API_KEY as string;
|
const genAI = new GoogleGenerativeAI(GEMINI_API_KEY);
|
||||||
const jinaToken = process.env.JINA_API_KEY as string;
|
|
||||||
if (!apiKey) throw new Error("GEMINI_API_KEY not found");
|
|
||||||
if (!jinaToken) throw new Error("JINA_API_KEY not found");
|
|
||||||
|
|
||||||
const modelName = 'gemini-1.5-flash';
|
|
||||||
const genAI = new GoogleGenerativeAI(apiKey);
|
|
||||||
|
|
||||||
const question = process.argv[2] || "";
|
const question = process.argv[2] || "";
|
||||||
getResponse(question);
|
getResponse(question);
|
||||||
|
|||||||
22
src/config.ts
Normal file
22
src/config.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import dotenv from 'dotenv';
|
||||||
|
import { ProxyAgent, setGlobalDispatcher } from 'undici';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
// Setup the proxy globally if present
|
||||||
|
if (process.env.https_proxy) {
|
||||||
|
try {
|
||||||
|
const proxyUrl = new URL(process.env.https_proxy).toString();
|
||||||
|
const dispatcher = new ProxyAgent({ uri: proxyUrl });
|
||||||
|
setGlobalDispatcher(dispatcher);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to set proxy:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GEMINI_API_KEY = process.env.GEMINI_API_KEY as string;
|
||||||
|
export const JINA_API_KEY = process.env.JINA_API_KEY as string;
|
||||||
|
export const MODEL_NAME = 'gemini-1.5-flash';
|
||||||
|
|
||||||
|
if (!GEMINI_API_KEY) throw new Error("GEMINI_API_KEY not found");
|
||||||
|
if (!JINA_API_KEY) throw new Error("JINA_API_KEY not found");
|
||||||
@@ -95,14 +95,15 @@ Set A: ${JSON.stringify(newQueries)}
|
|||||||
Set B: ${JSON.stringify(existingQueries)}`;
|
Set B: ${JSON.stringify(existingQueries)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function dedupQueries(newQueries: string[], existingQueries: string[]): Promise<string[]> {
|
export async function dedupQueries(newQueries: string[], existingQueries: string[]): Promise<{ unique_queries: string[], tokens: number }> {
|
||||||
try {
|
try {
|
||||||
const prompt = getPrompt(newQueries, existingQueries);
|
const prompt = getPrompt(newQueries, existingQueries);
|
||||||
const result = await model.generateContent(prompt);
|
const result = await model.generateContent(prompt);
|
||||||
const response = await result.response;
|
const response = await result.response;
|
||||||
|
const usage = response.usageMetadata;
|
||||||
const json = JSON.parse(response.text()) as DedupResponse;
|
const json = JSON.parse(response.text()) as DedupResponse;
|
||||||
console.log('Dedup:', json);
|
console.log('Dedup:', json);
|
||||||
return json.unique_queries;
|
return { unique_queries: json.unique_queries, tokens: usage?.totalTokenCount || 0 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in deduplication analysis:', error);
|
console.error('Error in deduplication analysis:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -127,4 +128,4 @@ async function main() {
|
|||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
main().catch(console.error);
|
main().catch(console.error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,16 +136,17 @@ ${diaryContext.join('\n')}
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function analyzeSteps(diaryContext: string[]): Promise<EvaluationResponse> {
|
export async function analyzeSteps(diaryContext: string[]): Promise<{ response: EvaluationResponse, tokens: number }> {
|
||||||
try {
|
try {
|
||||||
const prompt = getPrompt(diaryContext);
|
const prompt = getPrompt(diaryContext);
|
||||||
const result = await model.generateContent(prompt);
|
const result = await model.generateContent(prompt);
|
||||||
const response = await result.response;
|
const response = await result.response;
|
||||||
|
const usage = response.usageMetadata;
|
||||||
const json = JSON.parse(response.text()) as EvaluationResponse;
|
const json = JSON.parse(response.text()) as EvaluationResponse;
|
||||||
console.log('Rejection analysis:', json);
|
console.log('Rejection analysis:', json);
|
||||||
return json;
|
return { response: json, tokens: usage?.totalTokenCount || 0 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in answer evaluation:', error);
|
console.error('Error in answer evaluation:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,5 @@
|
|||||||
import { GoogleGenerativeAI, SchemaType } from "@google/generative-ai";
|
import { GoogleGenerativeAI, SchemaType } from "@google/generative-ai";
|
||||||
import dotenv from 'dotenv';
|
import { GEMINI_API_KEY, MODEL_NAME } from "../config";
|
||||||
import { ProxyAgent, setGlobalDispatcher } from "undici";
|
|
||||||
|
|
||||||
// Proxy setup
|
|
||||||
if (process.env.https_proxy) {
|
|
||||||
try {
|
|
||||||
const proxyUrl = new URL(process.env.https_proxy).toString();
|
|
||||||
const dispatcher = new ProxyAgent({ uri: proxyUrl });
|
|
||||||
setGlobalDispatcher(dispatcher);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set proxy:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const apiKey = process.env.GEMINI_API_KEY;
|
|
||||||
if (!apiKey) {
|
|
||||||
throw new Error("GEMINI_API_KEY not found in environment variables");
|
|
||||||
}
|
|
||||||
|
|
||||||
type EvaluationResponse = {
|
type EvaluationResponse = {
|
||||||
is_valid_answer: boolean;
|
is_valid_answer: boolean;
|
||||||
@@ -39,11 +21,9 @@ const responseSchema = {
|
|||||||
required: ["is_valid_answer", "reasoning"]
|
required: ["is_valid_answer", "reasoning"]
|
||||||
};
|
};
|
||||||
|
|
||||||
const modelName = 'gemini-1.5-flash';
|
const genAI = new GoogleGenerativeAI(GEMINI_API_KEY);
|
||||||
|
|
||||||
const genAI = new GoogleGenerativeAI(apiKey);
|
|
||||||
const model = genAI.getGenerativeModel({
|
const model = genAI.getGenerativeModel({
|
||||||
model: modelName,
|
model: MODEL_NAME,
|
||||||
generationConfig: {
|
generationConfig: {
|
||||||
temperature: 0,
|
temperature: 0,
|
||||||
responseMimeType: "application/json",
|
responseMimeType: "application/json",
|
||||||
@@ -93,14 +73,15 @@ Question: ${JSON.stringify(question)}
|
|||||||
Answer: ${JSON.stringify(answer)}`;
|
Answer: ${JSON.stringify(answer)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function evaluateAnswer(question: string, answer: string): Promise<EvaluationResponse> {
|
export async function evaluateAnswer(question: string, answer: string): Promise<{ response: EvaluationResponse, tokens: number }> {
|
||||||
try {
|
try {
|
||||||
const prompt = getPrompt(question, answer);
|
const prompt = getPrompt(question, answer);
|
||||||
const result = await model.generateContent(prompt);
|
const result = await model.generateContent(prompt);
|
||||||
const response = await result.response;
|
const response = await result.response;
|
||||||
|
const usage = response.usageMetadata;
|
||||||
const json = JSON.parse(response.text()) as EvaluationResponse;
|
const json = JSON.parse(response.text()) as EvaluationResponse;
|
||||||
console.log('Evaluation:', json);
|
console.log('Evaluation:', json);
|
||||||
return json;
|
return { response: json, tokens: usage?.totalTokenCount || 0 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in answer evaluation:', error);
|
console.error('Error in answer evaluation:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -130,4 +111,4 @@ async function main() {
|
|||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
main().catch(console.error);
|
main().catch(console.error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,5 @@
|
|||||||
import {GoogleGenerativeAI, SchemaType} from "@google/generative-ai";
|
import {GoogleGenerativeAI, SchemaType} from "@google/generative-ai";
|
||||||
import dotenv from 'dotenv';
|
import { GEMINI_API_KEY, MODEL_NAME } from "../config";
|
||||||
import {ProxyAgent, setGlobalDispatcher} from "undici";
|
|
||||||
|
|
||||||
// Proxy setup remains the same
|
|
||||||
if (process.env.https_proxy) {
|
|
||||||
try {
|
|
||||||
const proxyUrl = new URL(process.env.https_proxy).toString();
|
|
||||||
const dispatcher = new ProxyAgent({uri: proxyUrl});
|
|
||||||
setGlobalDispatcher(dispatcher);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set proxy:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const apiKey = process.env.GEMINI_API_KEY;
|
|
||||||
if (!apiKey) {
|
|
||||||
throw new Error("GEMINI_API_KEY not found in environment variables");
|
|
||||||
}
|
|
||||||
|
|
||||||
type KeywordsResponse = {
|
type KeywordsResponse = {
|
||||||
keywords: string[];
|
keywords: string[];
|
||||||
@@ -44,11 +26,9 @@ const responseSchema = {
|
|||||||
required: ["thought", "keywords"]
|
required: ["thought", "keywords"]
|
||||||
};
|
};
|
||||||
|
|
||||||
const modelName = 'gemini-1.5-flash';
|
const genAI = new GoogleGenerativeAI(GEMINI_API_KEY);
|
||||||
|
|
||||||
const genAI = new GoogleGenerativeAI(apiKey);
|
|
||||||
const model = genAI.getGenerativeModel({
|
const model = genAI.getGenerativeModel({
|
||||||
model: modelName,
|
model: MODEL_NAME,
|
||||||
generationConfig: {
|
generationConfig: {
|
||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
responseMimeType: "application/json",
|
responseMimeType: "application/json",
|
||||||
@@ -103,16 +83,15 @@ Now, process this query:
|
|||||||
Input Query: ${query}`;
|
Input Query: ${query}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function rewriteQuery(query: string): Promise<string[]> {
|
export async function rewriteQuery(query: string): Promise<{ keywords: string[], tokens: number }> {
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const prompt = getPrompt(query);
|
const prompt = getPrompt(query);
|
||||||
const result = await model.generateContent(prompt);
|
const result = await model.generateContent(prompt);
|
||||||
const response = await result.response;
|
const response = await result.response;
|
||||||
|
const usage = response.usageMetadata;
|
||||||
const json = JSON.parse(response.text()) as KeywordsResponse;
|
const json = JSON.parse(response.text()) as KeywordsResponse;
|
||||||
console.log('Rewriter:', json)
|
console.log('Rewriter:', json)
|
||||||
return json.keywords;
|
return { keywords: json.keywords, tokens: usage?.totalTokenCount || 0 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in query rewriting:', error);
|
console.error('Error in query rewriting:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -134,4 +113,4 @@ async function main() {
|
|||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
main().catch(console.error);
|
main().catch(console.error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user