refactor: knowledge as msg

This commit is contained in:
Han Xiao 2025-03-13 14:01:35 +08:00
parent 59b2daf66b
commit 0e0630b727
5 changed files with 126 additions and 135 deletions

View File

@ -49,6 +49,58 @@ async function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
} }
function BuildMsgsFromKnowledge(knowledge: KnowledgeItem[]) : CoreMessage[] {
// build user, assistant pair messages from knowledge
const messages: CoreMessage[] = [];
knowledge.forEach(k => {
messages.push({role: 'user', content: k.question.trim()});
const aMsg = `
${k.updated && (k.type === 'url' || k.type === 'side-info') ? `
<answer-datetime>
${k.updated}
</answer-datetime>
` : ''}
${k.references && k.type === 'url' ? `
<url>
${k.references[0]}
</url>
` : ''}
${k.answer}
`.trim();
messages.push({role: 'assistant', content: removeExtraLineBreaks(aMsg)});
});
return messages;
}
function composeMsgs(messages: CoreMessage[], knowledge: KnowledgeItem[], question: string, finalAnswerPIP?: string) {
const msgs = [...messages, ...BuildMsgsFromKnowledge(knowledge)];
const userContent = `
${knowledge.length>0 ? `
Based on what you learned from our previous messages, what is your answer to the question below?
`: ''}
${question}
${finalAnswerPIP ? `
<answer-requirements>
- You provide deep, unexpected insights, identifying hidden patterns and connections, and creating "aha moments.".
- You break conventional thinking, establish unique cross-disciplinary connections, and bring new perspectives to the user.
${finalAnswerPIP}
</answer-requirements>` : ''}
`.trim();
// only add if the last user msg is not the same
// first find the last message whose role is 'user'
const lastUserMsg = msgs.filter(m => m.role === 'user').pop();
if ((lastUserMsg?.content as string).trim() !== userContent) {
msgs.push({role: 'user', content: removeExtraLineBreaks(userContent)});
}
return msgs;
}
function getPrompt( function getPrompt(
context?: string[], context?: string[],
@ -59,7 +111,6 @@ function getPrompt(
allowRead: boolean = true, allowRead: boolean = true,
allowSearch: boolean = true, allowSearch: boolean = true,
allowCoding: boolean = true, allowCoding: boolean = true,
badContext?: { question: string, answer: string, evaluation: string, recap: string; blame: string; improvement: string; }[],
knowledge?: KnowledgeItem[], knowledge?: KnowledgeItem[],
allURLs?: BoostedSearchSnippet[], allURLs?: BoostedSearchSnippet[],
beastMode?: boolean, beastMode?: boolean,
@ -71,42 +122,9 @@ function getPrompt(
sections.push(`Current date: ${new Date().toUTCString()} sections.push(`Current date: ${new Date().toUTCString()}
You are an advanced AI research agent from Jina AI. You are specialized in multistep reasoning. You are an advanced AI research agent from Jina AI. You are specialized in multistep reasoning.
Using your training data and prior lessons learned, answer the user question with absolute certainty. Using your best knowledge, conversation with the user and lessons learned, answer the user question with absolute certainty.
`); `);
// Add knowledge section if exists
if (knowledge?.length) {
const knowledgeItems = knowledge
.map((k, i) => `
<knowledge-${i + 1}>
<question>
${k.question}
</question>
<answer>
${k.answer}
</answer>
${k.updated && (k.type === 'url' || k.type === 'side-info') ? `
<answer-datetime>
${k.updated}
</answer-datetime>
` : ''}
${k.references && k.type === 'url' ? `
<url>
${k.references[0]}
</url>
` : ''}
</knowledge-${i + 1}>
`)
.join('\n\n');
sections.push(`
You have successfully gathered some knowledge which might be useful for answering the original question. Here is the knowledge you have gathered so far:
<knowledge>
${knowledgeItems}
</knowledge>
`);
}
// Add context section if exists // Add context section if exists
if (context?.length) { if (context?.length) {
@ -119,37 +137,6 @@ ${context.join('\n')}
`); `);
} }
// Add bad context section if exists
if (badContext?.length) {
const attempts = badContext
.map((c, i) => `
<attempt-${i + 1}>
- Question: ${c.question}
- Answer: ${c.answer}
- Reject Reason: ${c.evaluation}
- Actions Recap: ${c.recap}
- Actions Blame: ${c.blame}
</attempt-${i + 1}>
`)
.join('\n\n');
const learnedStrategy = badContext.map(c => c.improvement).join('\n');
sections.push(`
Also, you have tried the following actions but failed to find the answer to the question:
<bad-attempts>
${attempts}
</bad-attempts>
Based on the failed attempts, you have learned the following strategy:
<learned-strategy>
${learnedStrategy}
</learned-strategy>
`);
}
// Build actions section // Build actions section
if (allowRead) { if (allowRead) {
@ -169,14 +156,6 @@ ${urlList}
`); `);
} }
if (allowCoding) {
actionSections.push(`
<action-coding>
- This JavaScript-based solution helps you handle programming tasks like counting, filtering, transforming, sorting, regex extraction, and data processing.
- Simply describe your problem in the "codingIssue" field. Include actual values for small inputs or variable names for larger datasets.
- No code writing is required senior engineers will handle the implementation.
</action-coding>`);
}
if (allowSearch) { if (allowSearch) {
@ -216,7 +195,7 @@ ${allKeywords.join('\n')}
PRIME DIRECTIVE: PRIME DIRECTIVE:
- DEMOLISH ALL HESITATION! ANY RESPONSE SURPASSES SILENCE! - DEMOLISH ALL HESITATION! ANY RESPONSE SURPASSES SILENCE!
- PARTIAL STRIKES AUTHORIZED - DEPLOY WITH FULL CONTEXTUAL FIREPOWER - PARTIAL STRIKES AUTHORIZED - DEPLOY WITH FULL CONTEXTUAL FIREPOWER
- TACTICAL REUSE FROM <bad-attempts> SANCTIONED - TACTICAL REUSE FROM PREVIOUS CONVERSATION SANCTIONED
- WHEN IN DOUBT: UNLEASH CALCULATED STRIKES BASED ON AVAILABLE INTEL! - WHEN IN DOUBT: UNLEASH CALCULATED STRIKES BASED ON AVAILABLE INTEL!
FAILURE IS NOT AN OPTION. EXECUTE WITH EXTREME PREJUDICE! FAILURE IS NOT AN OPTION. EXECUTE WITH EXTREME PREJUDICE!
@ -227,10 +206,19 @@ FAILURE IS NOT AN OPTION. EXECUTE WITH EXTREME PREJUDICE! ⚡️
if (allowReflect) { if (allowReflect) {
actionSections.push(` actionSections.push(`
<action-reflect> <action-reflect>
- Think slowly and planning lookahead. Examine <question>, <context>, <knowledge>, <bad-attempts>, and <learned-strategy> to identify knowledge gaps. - Think slowly and planning lookahead. Examine <question>, <context>, previous conversation with users to identify knowledge gaps.
- Reflect the gaps and plan a list key clarifying questions that deeply related to the original question and lead to the answer - Reflect the gaps and plan a list key clarifying questions that deeply related to the original question and lead to the answer
</action-reflect> </action-reflect>
`); `);
}
if (allowCoding) {
actionSections.push(`
<action-coding>
- This JavaScript-based solution helps you handle programming tasks like counting, filtering, transforming, sorting, regex extraction, and data processing.
- Simply describe your problem in the "codingIssue" field. Include actual values for small inputs or variable names for larger datasets.
- No code writing is required senior engineers will handle the implementation.
</action-coding>`);
} }
sections.push(` sections.push(`
@ -253,15 +241,6 @@ function updateContext(step: any) {
allContext.push(step) allContext.push(step)
} }
function replaceLastUserMsg(messages: Array<CoreMessage>, content: string) {
return messages.map((m, i) => {
if (m.role === 'user' && i === messages.length - 1) {
return {...m, content}
}
return m
});
}
export async function getResponse(question?: string, export async function getResponse(question?: string,
tokenBudget: number = 1_000_000, tokenBudget: number = 1_000_000,
@ -275,6 +254,9 @@ export async function getResponse(question?: string,
let badAttempts = 0; let badAttempts = 0;
question = question?.trim() as string; question = question?.trim() as string;
// remove incoming system messages to avoid override
messages = messages?.filter(m => m.role !== 'system');
if (messages && messages.length > 0) { if (messages && messages.length > 0) {
// 2 cases // 2 cases
const lastContent = messages[messages.length - 1].content; const lastContent = messages[messages.length - 1].content;
@ -303,7 +285,6 @@ export async function getResponse(question?: string,
const allKeywords = []; const allKeywords = [];
const allKnowledge: KnowledgeItem[] = []; // knowledge are intermedidate questions that are answered const allKnowledge: KnowledgeItem[] = []; // knowledge are intermedidate questions that are answered
const badContext = [];
let diaryContext = []; let diaryContext = [];
let weightedURLs: BoostedSearchSnippet[] = []; let weightedURLs: BoostedSearchSnippet[] = [];
let allowAnswer = true; let allowAnswer = true;
@ -312,6 +293,7 @@ export async function getResponse(question?: string,
let allowReflect = true; let allowReflect = true;
let allowCoding = true; let allowCoding = true;
let system = ''; let system = '';
let msgWithKnowledge: CoreMessage[] = [];
let thisStep: StepAction = {action: 'answer', answer: '', references: [], think: '', isFinal: false}; let thisStep: StepAction = {action: 'answer', answer: '', references: [], think: '', isFinal: false};
const allURLs: Record<string, SearchSnippet> = {}; const allURLs: Record<string, SearchSnippet> = {};
@ -327,8 +309,9 @@ export async function getResponse(question?: string,
const budgetPercentage = (context.tokenTracker.getTotalUsage().totalTokens / tokenBudget * 100).toFixed(2); const budgetPercentage = (context.tokenTracker.getTotalUsage().totalTokens / tokenBudget * 100).toFixed(2);
console.log(`Step ${totalStep} / Budget used ${budgetPercentage}%`); console.log(`Step ${totalStep} / Budget used ${budgetPercentage}%`);
console.log('Gaps:', gaps); console.log('Gaps:', gaps);
allowReflect = allowReflect && (gaps.length <= 1); allowReflect = allowReflect && (gaps.length <= MAX_REFLECT_PER_STEP);
const currentQuestion: string = gaps.length > 0 ? gaps.shift()! : question // rotating question from gaps
const currentQuestion: string = gaps[totalStep % gaps.length];
// if (!evaluationMetrics[currentQuestion]) { // if (!evaluationMetrics[currentQuestion]) {
// evaluationMetrics[currentQuestion] = // evaluationMetrics[currentQuestion] =
// await evaluateQuestion(currentQuestion, context, SchemaGen) // await evaluateQuestion(currentQuestion, context, SchemaGen)
@ -375,17 +358,17 @@ export async function getResponse(question?: string,
allowRead, allowRead,
allowSearch, allowSearch,
allowCoding, allowCoding,
badContext,
allKnowledge, allKnowledge,
weightedURLs, weightedURLs,
false, false,
); );
schema = SchemaGen.getAgentSchema(allowReflect, allowRead, allowAnswer, allowSearch, allowCoding, finalAnswerPIP, currentQuestion) schema = SchemaGen.getAgentSchema(allowReflect, allowRead, allowAnswer, allowSearch, allowCoding, currentQuestion)
msgWithKnowledge = composeMsgs(messages, allKnowledge, currentQuestion, currentQuestion === question ? finalAnswerPIP : undefined);
const result = await generator.generateObject({ const result = await generator.generateObject({
model: 'agent', model: 'agent',
schema, schema,
system, system,
messages: replaceLastUserMsg(messages, currentQuestion), messages: msgWithKnowledge,
}); });
thisStep = { thisStep = {
action: result.object.action, action: result.object.action,
@ -394,7 +377,7 @@ export async function getResponse(question?: string,
} as StepAction; } as StepAction;
// print allowed and chose action // print allowed and chose action
const actionsStr = [allowSearch, allowRead, allowAnswer, allowReflect, allowCoding].map((a, i) => a ? ['search', 'read', 'answer', 'reflect'][i] : null).filter(a => a).join(', '); const actionsStr = [allowSearch, allowRead, allowAnswer, allowReflect, allowCoding].map((a, i) => a ? ['search', 'read', 'answer', 'reflect'][i] : null).filter(a => a).join(', ');
console.log(`${thisStep.action} <- [${actionsStr}]`); console.log(`${currentQuestion}: ${thisStep.action} <- [${actionsStr}]`);
console.log(thisStep) console.log(thisStep)
context.actionTracker.trackAction({totalStep, thisStep, gaps, badAttempts}); context.actionTracker.trackAction({totalStep, thisStep, gaps, badAttempts});
@ -447,6 +430,10 @@ export async function getResponse(question?: string,
SchemaGen, SchemaGen,
currentQuestion currentQuestion
); );
if (!evaluationMetrics[currentQuestion].includes('attribution')) {
evaluationMetrics[currentQuestion].push('attribution')
}
} }
updateContext({ updateContext({
@ -459,7 +446,9 @@ export async function getResponse(question?: string,
let evaluation: EvaluationResponse = {pass: true, think: ''}; let evaluation: EvaluationResponse = {pass: true, think: ''};
if (evaluationMetrics[currentQuestion].length > 0) { if (evaluationMetrics[currentQuestion].length > 0) {
context.actionTracker.trackThink('eval_first', SchemaGen.languageCode) context.actionTracker.trackThink('eval_first', SchemaGen.languageCode)
evaluation = await evaluateAnswer(currentQuestion, thisStep, evaluation = await evaluateAnswer(
currentQuestion,
thisStep,
evaluationMetrics[currentQuestion], evaluationMetrics[currentQuestion],
context, context,
allKnowledge, allKnowledge,
@ -512,19 +501,29 @@ ${evaluation.think}
// store the bad context and reset the diary context // store the bad context and reset the diary context
const errorAnalysis = await analyzeSteps(diaryContext, context, SchemaGen); const errorAnalysis = await analyzeSteps(diaryContext, context, SchemaGen);
badContext.push({ allKnowledge.push({
question: currentQuestion, question: `
answer: thisStep.answer, Why is the following answer bad for the question? Please reflect
evaluation: evaluation.think,
...errorAnalysis
});
if (errorAnalysis.questionsToAnswer) { <question>
errorAnalysis.questionsToAnswer = chooseK((await dedupQueries(errorAnalysis.questionsToAnswer, allQuestions, context.tokenTracker)).unique_queries, MAX_REFLECT_PER_STEP); ${currentQuestion}
gaps.push(...errorAnalysis.questionsToAnswer); </question>
allQuestions.push(...errorAnalysis.questionsToAnswer);
gaps.push(question); // always keep the original question in the gaps <answer>
} ${thisStep.answer}
</answer>
`,
answer: `
${evaluation.think}
${errorAnalysis.recap}
${errorAnalysis.blame}
${errorAnalysis.improvement}
`,
type: 'qa',
})
badAttempts++; badAttempts++;
allowAnswer = false; // disable answer action in the immediate next step allowAnswer = false; // disable answer action in the immediate next step
@ -533,6 +532,7 @@ ${evaluation.think}
} }
} }
} else if (evaluation.pass) { } else if (evaluation.pass) {
// solved a gap question
diaryContext.push(` diaryContext.push(`
At step ${step}, you took **answer** action. You found a good answer to the sub-question: At step ${step}, you took **answer** action. You found a good answer to the sub-question:
@ -554,6 +554,8 @@ Although you solved a sub-question, you still need to find the answer to the ori
type: 'qa', type: 'qa',
updated: formatDateBasedOnType(new Date(), 'full') updated: formatDateBasedOnType(new Date(), 'full')
}); });
// solved sub-question!
gaps.splice(gaps.indexOf(currentQuestion), 1);
} }
} else if (thisStep.action === 'reflect' && thisStep.questionsToAnswer) { } else if (thisStep.action === 'reflect' && thisStep.questionsToAnswer) {
thisStep.questionsToAnswer = chooseK((await dedupQueries(thisStep.questionsToAnswer, allQuestions, context.tokenTracker)).unique_queries, MAX_REFLECT_PER_STEP); thisStep.questionsToAnswer = chooseK((await dedupQueries(thisStep.questionsToAnswer, allQuestions, context.tokenTracker)).unique_queries, MAX_REFLECT_PER_STEP);
@ -569,7 +571,6 @@ You will now figure out the answers to these sub-questions and see if they can h
`); `);
gaps.push(...newGapQuestions); gaps.push(...newGapQuestions);
allQuestions.push(...newGapQuestions); allQuestions.push(...newGapQuestions);
gaps.push(question); // always keep the original question in the gaps
updateContext({ updateContext({
totalStep, totalStep,
...thisStep, ...thisStep,
@ -784,11 +785,11 @@ But unfortunately, you failed to solve the issue. You need to think out of the b
} }
} }
await storeContext(system, schema, {allContext, allKeywords, allQuestions, allKnowledge, weightedURLs}, totalStep); await storeContext(system, schema, {allContext, allKeywords, allQuestions, allKnowledge, weightedURLs, msgWithKnowledge}, totalStep);
await sleep(STEP_SLEEP); await sleep(STEP_SLEEP);
} }
await storeContext(system, schema, {allContext, allKeywords, allQuestions, allKnowledge, weightedURLs}, totalStep); await storeContext(system, schema, {allContext, allKeywords, allQuestions, allKnowledge, weightedURLs, msgWithKnowledge}, totalStep);
if (!(thisStep as AnswerAction).isFinal) { if (!(thisStep as AnswerAction).isFinal) {
console.log('Enter Beast mode!!!') console.log('Enter Beast mode!!!')
// any answer is better than no answer, humanity last resort // any answer is better than no answer, humanity last resort
@ -803,18 +804,18 @@ But unfortunately, you failed to solve the issue. You need to think out of the b
false, false,
false, false,
false, false,
badContext,
allKnowledge, allKnowledge,
weightedURLs, weightedURLs,
true, true,
); );
schema = SchemaGen.getAgentSchema(false, false, true, false, false, finalAnswerPIP, question); schema = SchemaGen.getAgentSchema(false, false, true, false, false, question);
msgWithKnowledge = composeMsgs(messages, allKnowledge, question, finalAnswerPIP);
const result = await generator.generateObject({ const result = await generator.generateObject({
model: 'agentBeastMode', model: 'agentBeastMode',
schema, schema,
system, system,
messages messages: msgWithKnowledge
}); });
thisStep = { thisStep = {
action: result.object.action, action: result.object.action,
@ -828,7 +829,7 @@ But unfortunately, you failed to solve the issue. You need to think out of the b
(thisStep as AnswerAction).mdAnswer = buildMdFromAnswer((thisStep as AnswerAction)) (thisStep as AnswerAction).mdAnswer = buildMdFromAnswer((thisStep as AnswerAction))
console.log(thisStep) console.log(thisStep)
await storeContext(system, schema, {allContext, allKeywords, allQuestions, allKnowledge, weightedURLs}, totalStep); await storeContext(system, schema, {allContext, allKeywords, allQuestions, allKnowledge, weightedURLs, msgWithKnowledge}, totalStep);
// max return 300 urls // max return 300 urls
const returnedURLs = weightedURLs.slice(0, 50).map(r => r.url); const returnedURLs = weightedURLs.slice(0, 50).map(r => r.url);
@ -846,10 +847,11 @@ async function storeContext(prompt: string, schema: any, memory: {
allQuestions: string[]; allQuestions: string[];
allKnowledge: KnowledgeItem[]; allKnowledge: KnowledgeItem[];
weightedURLs: BoostedSearchSnippet[]; weightedURLs: BoostedSearchSnippet[];
msgWithKnowledge: CoreMessage[];
} }
, step: number) { , step: number) {
const {allContext, allKeywords, allQuestions, allKnowledge, weightedURLs} = memory; const {allContext, allKeywords, allQuestions, allKnowledge, weightedURLs, msgWithKnowledge} = memory;
if ((process as any).asyncLocalContext?.available?.()) { if ((process as any).asyncLocalContext?.available?.()) {
(process as any).asyncLocalContext.ctx.promptContext = { (process as any).asyncLocalContext.ctx.promptContext = {
@ -877,6 +879,7 @@ ${JSON.stringify(zodToJsonSchema(schema), null, 2)}
await fs.writeFile('questions.json', JSON.stringify(allQuestions, null, 2)); await fs.writeFile('questions.json', JSON.stringify(allQuestions, null, 2));
await fs.writeFile('knowledge.json', JSON.stringify(allKnowledge, null, 2)); await fs.writeFile('knowledge.json', JSON.stringify(allKnowledge, null, 2));
await fs.writeFile('urls.json', JSON.stringify(weightedURLs, null, 2)); await fs.writeFile('urls.json', JSON.stringify(weightedURLs, null, 2));
await fs.writeFile('messages.json', JSON.stringify(msgWithKnowledge, null, 2));
} catch (error) { } catch (error) {
console.error('Context storage failed:', error); console.error('Context storage failed:', error);
} }

View File

@ -83,11 +83,7 @@ The answer is not definitive and fails to provide the requested information. La
"blame": "The root cause of failure was getting stuck in a repetitive search pattern without adapting the strategy. Steps 4-5 repeated the same search, and step 6 deviated to less reliable entertainment sources instead of exploring business journals, news articles, or professional databases. Additionally, the process didn't attempt to triangulate age through indirect information like education history or career milestones.", "blame": "The root cause of failure was getting stuck in a repetitive search pattern without adapting the strategy. Steps 4-5 repeated the same search, and step 6 deviated to less reliable entertainment sources instead of exploring business journals, news articles, or professional databases. Additionally, the process didn't attempt to triangulate age through indirect information like education history or career milestones.",
"improvement": "1. Avoid repeating identical searches and implement a strategy to track previously searched terms. 2. When direct age/birthdate searches fail, try indirect approaches like: searching for earliest career mentions, finding university graduation years, or identifying first company founding dates. 3. Focus on high-quality business sources and avoid entertainment websites for professional information. 4. Consider using industry event appearances or conference presentations where age-related context might be mentioned. 5. If exact age cannot be determined, provide an estimated range based on career timeline and professional achievements.", "improvement": "1. Avoid repeating identical searches and implement a strategy to track previously searched terms. 2. When direct age/birthdate searches fail, try indirect approaches like: searching for earliest career mentions, finding university graduation years, or identifying first company founding dates. 3. Focus on high-quality business sources and avoid entertainment websites for professional information. 4. Consider using industry event appearances or conference presentations where age-related context might be mentioned. 5. If exact age cannot be determined, provide an estimated range based on career timeline and professional achievements.",
"questionsToAnswer": [
"What alternative professional databases or news archives could provide reliable biographical information?",
"How can we use education history or career milestones to estimate age range?"
]
} }
</output> </output>
</example>`, </example>`,

View File

@ -1,9 +1,7 @@
import {GenerateObjectResult} from 'ai'; import {GenerateObjectResult} from 'ai';
import {AnswerAction, EvaluationResponse, EvaluationType, KnowledgeItem, PromptPair, TrackerContext} from '../types'; import {AnswerAction, EvaluationResponse, EvaluationType, KnowledgeItem, PromptPair, TrackerContext} from '../types';
import {readUrl} from "./read";
import {ObjectGeneratorSafe} from "../utils/safe-generator"; import {ObjectGeneratorSafe} from "../utils/safe-generator";
import {Schemas} from "../utils/schemas"; import {Schemas} from "../utils/schemas";
import {removeAllLineBreaks} from "../utils/text-tools";
const TOOL_NAME = 'evaluator'; const TOOL_NAME = 'evaluator';

View File

@ -173,7 +173,6 @@ export type ErrorAnalysisResponse = {
recap: string; recap: string;
blame: string; blame: string;
improvement: string; improvement: string;
questionsToAnswer: string[];
}; };

View File

@ -4,7 +4,7 @@ import {EvaluationType, PromptPair} from "../types";
export const MAX_URLS_PER_STEP = 4 export const MAX_URLS_PER_STEP = 4
export const MAX_QUERIES_PER_STEP = 7 export const MAX_QUERIES_PER_STEP = 7
export const MAX_REFLECT_PER_STEP = 3 export const MAX_REFLECT_PER_STEP = 1
function getLanguagePrompt(question: string): PromptPair { function getLanguagePrompt(question: string): PromptPair {
return { return {
@ -112,11 +112,7 @@ export class Schemas {
return z.object({ return z.object({
recap: z.string().describe('Recap of the actions taken and the steps conducted in first person narrative.').max(500), recap: z.string().describe('Recap of the actions taken and the steps conducted in first person narrative.').max(500),
blame: z.string().describe(`Which action or the step was the root cause of the answer rejection. ${this.getLanguagePrompt()}`).max(500), blame: z.string().describe(`Which action or the step was the root cause of the answer rejection. ${this.getLanguagePrompt()}`).max(500),
improvement: z.string().describe(`Suggested key improvement for the next iteration, do not use bullet points, be concise and hot-take vibe. ${this.getLanguagePrompt()}`).max(500), improvement: z.string().describe(`Suggested key improvement for the next iteration, do not use bullet points, be concise and hot-take vibe. ${this.getLanguagePrompt()}`).max(500)
questionsToAnswer: z.array(
z.string().describe("each question must be a single line, concise and clear. not composite or compound, less than 20 words.")
).max(MAX_REFLECT_PER_STEP)
.describe(`List of most important reflect questions to fill the knowledge gaps. Maximum provide ${MAX_REFLECT_PER_STEP} reflect questions.`)
}); });
} }
@ -200,7 +196,7 @@ export class Schemas {
} }
getAgentSchema(allowReflect: boolean, allowRead: boolean, allowAnswer: boolean, allowSearch: boolean, allowCoding: boolean, getAgentSchema(allowReflect: boolean, allowRead: boolean, allowAnswer: boolean, allowSearch: boolean, allowCoding: boolean,
finalAnswerPIP?: string, currentQuestion?: string): z.ZodObject<any> { currentQuestion?: string): z.ZodObject<any> {
const actionSchemas: Record<string, z.ZodOptional<any>> = {}; const actionSchemas: Record<string, z.ZodOptional<any>> = {};
if (allowSearch) { if (allowSearch) {
@ -227,14 +223,13 @@ export class Schemas {
references: z.array( references: z.array(
z.object({ z.object({
exactQuote: z.string().describe("Exact relevant quote from the document, must be a soundbite, short and to the point, no fluff").max(30), exactQuote: z.string().describe("Exact relevant quote from the document, must be a soundbite, short and to the point, no fluff").max(30),
url: z.string().describe("source URL; must be copy directly from existing knowledge's <url>, avoid example.com or any placeholder fake URLs").max(100), url: z.string().describe("source URL; must be copy directly from previous message's <url>, avoid example.com or any placeholder fake URLs").max(100),
dateTime: z.string().describe("Use original knowledge's <dateime> if available.").max(16), dateTime: z.string().describe("Use original message's <answer-dateime> if available.").max(16),
}).required() }).required()
).describe("Required when action='answer'. Must be an array of references that support the answer, each reference must contain an exact quote, URL and datetime"), ).describe("Required when action='answer'. Must be an array of references that support the answer, each reference must contain an exact quote, URL and datetime"),
answer: z.string() answer: z.string()
.describe(`Required when action='answer'. .describe(`Required when action='answer'.
${finalAnswerPIP}
Use all your knowledge you have collected, cover multiple aspects if needed. Use all your knowledge you have collected, cover multiple aspects if needed.
Must be definitive, no ambiguity, no uncertainty, no disclaimers. Must in ${this.languageStyle} and confident. Must be definitive, no ambiguity, no uncertainty, no disclaimers. Must in ${this.languageStyle} and confident.
Use markdown footnote syntax like [^1], [^2] to refer the corresponding reference item. Use markdown footnote syntax like [^1], [^2] to refer the corresponding reference item.
@ -249,13 +244,13 @@ export class Schemas {
questionsToAnswer: z.array( questionsToAnswer: z.array(
z.string().describe(` z.string().describe(`
Ensure each reflection question: Ensure each reflection question:
- Cuts to core emotional truths while staying anchored to the original question - Cuts to core emotional truths while staying anchored to <og-question>
- Transforms surface-level problems into deeper psychological insights - Transforms surface-level problems into deeper psychological insights, helps answer <og-question>
- Makes the unconscious conscious - Makes the unconscious conscious
- NEVER pose general questions like: "How can I verify the accuracy of information before including it in my answer?", "What information was actually contained in the URLs I found?" - NEVER pose general questions like: "How can I verify the accuracy of information before including it in my answer?", "What information was actually contained in the URLs I found?", "How can i tell if a source is reliable?".
`) `)
).max(MAX_REFLECT_PER_STEP) ).max(MAX_REFLECT_PER_STEP)
.describe(`Required when action='reflect'. Reflection and planing, generate a list of most important questions to fill the knowledge gaps to the original question ${currentQuestion}. Maximum provide ${MAX_REFLECT_PER_STEP} reflect questions.`) .describe(`Required when action='reflect'. Reflection and planing, generate a list of most important questions to fill the knowledge gaps to <og-question> ${currentQuestion} </og-question>. Maximum provide ${MAX_REFLECT_PER_STEP} reflect questions.`)
}).optional() }).optional()
} }