feat(agent): Enhance agent components with new actions and improve loading animations
This commit is contained in:
parent
2805417307
commit
7b47d3dacb
9 changed files with 166 additions and 79 deletions
|
|
@ -11,3 +11,17 @@
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
@keyframes high-bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-9px);
|
||||
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import {
|
|||
Search,
|
||||
Zap,
|
||||
Microscope,
|
||||
Ban,
|
||||
CircleCheck,
|
||||
ListPlus,
|
||||
} from 'lucide-react';
|
||||
import { AgentActionEvent } from './ChatWindow';
|
||||
|
||||
|
|
@ -42,6 +45,22 @@ const AgentActionDisplay = ({
|
|||
return <Zap size={size} className="text-[#9C27B0]" />;
|
||||
case 'PROCEEDING_WITH_FULL_ANALYSIS':
|
||||
return <Microscope size={size} className="text-[#9C27B0]" />;
|
||||
case 'SKIPPING_IRRELEVANT_SOURCE':
|
||||
return <Ban size={size} className="text-red-600 dark:text-red-500" />;
|
||||
case 'CONTEXT_UPDATED':
|
||||
return (
|
||||
<ListPlus
|
||||
size={size}
|
||||
className="text-green-600 dark:text-green-500"
|
||||
/>
|
||||
);
|
||||
case 'INFORMATION_GATHERING_COMPLETE':
|
||||
return (
|
||||
<CircleCheck
|
||||
size={size}
|
||||
className="text-green-600 dark:text-green-500"
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <Bot size={size} className="text-[#9C27B0]" />;
|
||||
}
|
||||
|
|
@ -64,12 +83,12 @@ const AgentActionDisplay = ({
|
|||
latestEvent.action === 'INFORMATION_GATHERING_COMPLETE'
|
||||
? 'Agent Log'
|
||||
: formatActionName(latestEvent.action)}
|
||||
{isLoading &&
|
||||
{/* {isLoading &&
|
||||
latestEvent.action !== 'INFORMATION_GATHERING_COMPLETE' && (
|
||||
<span className="ml-2 inline-block align-middle">
|
||||
<span className="animate-spin inline-block w-4 h-4 border-2 border-t-transparent border-[#9C27B0] rounded-full align-middle"></span>
|
||||
</span>
|
||||
)}
|
||||
)} */}
|
||||
</span>
|
||||
</div>
|
||||
{isExpanded ? (
|
||||
|
|
@ -103,7 +122,9 @@ const AgentActionDisplay = ({
|
|||
<div className="mt-2 text-sm text-black/60 dark:text-white/60">
|
||||
{event.details.sourceUrl && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Source:</span>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
Source:
|
||||
</span>
|
||||
<span className="truncate">
|
||||
<a href={event.details.sourceUrl} target="_blank">
|
||||
{event.details.sourceUrl}
|
||||
|
|
@ -113,14 +134,18 @@ const AgentActionDisplay = ({
|
|||
)}
|
||||
{event.details.skipReason && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Reason:</span>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
Reason:
|
||||
</span>
|
||||
<span>{event.details.skipReason}</span>
|
||||
</div>
|
||||
)}
|
||||
{event.details.searchQuery &&
|
||||
event.details.searchQuery !== event.details.query && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Search Query:</span>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
Search Query:
|
||||
</span>
|
||||
<span className="italic">
|
||||
"{event.details.searchQuery}"
|
||||
</span>
|
||||
|
|
@ -128,37 +153,45 @@ const AgentActionDisplay = ({
|
|||
)}
|
||||
{event.details.sourcesFound !== undefined && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Sources Found:</span>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
Sources Found:
|
||||
</span>
|
||||
<span>{event.details.sourcesFound}</span>
|
||||
</div>
|
||||
)}
|
||||
{/* {(event.details.documentCount !== undefined && event.details.documentCount > 0) && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Documents:</span>
|
||||
<span className="font-bold whitespace-nowrap">Documents:</span>
|
||||
<span>{event.details.documentCount}</span>
|
||||
</div>
|
||||
)} */}
|
||||
{event.details.contentLength !== undefined && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Content Length:</span>
|
||||
<span>{event.details.contentLength} chars</span>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
Content Length:
|
||||
</span>
|
||||
<span>{event.details.contentLength} characters</span>
|
||||
</div>
|
||||
)}
|
||||
{event.details.searchInstructions !== undefined && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Search Instructions:</span>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
Search Instructions:
|
||||
</span>
|
||||
<span>{event.details.searchInstructions}</span>
|
||||
</div>
|
||||
)}
|
||||
{event.details.previewCount !== undefined && (
|
||||
{/* {event.details.previewCount !== undefined && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Preview Sources:</span>
|
||||
<span className="font-bold whitespace-nowrap">Preview Sources:</span>
|
||||
<span>{event.details.previewCount}</span>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
{event.details.processingType && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Processing Type:</span>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
Processing Type:
|
||||
</span>
|
||||
<span className="capitalize">
|
||||
{event.details.processingType.replace('-', ' ')}
|
||||
</span>
|
||||
|
|
@ -166,51 +199,41 @@ const AgentActionDisplay = ({
|
|||
)}
|
||||
{event.details.insufficiencyReason && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Reason:</span>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
Reason:
|
||||
</span>
|
||||
<span>{event.details.insufficiencyReason}</span>
|
||||
</div>
|
||||
)}
|
||||
{event.details.reason && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Reason:</span>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
Reason:
|
||||
</span>
|
||||
<span>{event.details.reason}</span>
|
||||
</div>
|
||||
)}
|
||||
{event.details.taskCount !== undefined && (
|
||||
{/* {event.details.taskCount !== undefined && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Tasks:</span>
|
||||
<span className="font-bold whitespace-nowrap">Tasks:</span>
|
||||
<span>{event.details.taskCount}</span>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
{event.details.currentTask && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Current Task:</span>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
Current Task:
|
||||
</span>
|
||||
<span className="italic">
|
||||
"{event.details.currentTask}"
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{event.details.taskIndex !== undefined &&
|
||||
event.details.totalTasks !== undefined && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Progress:</span>
|
||||
<span>
|
||||
Task {event.details.taskIndex} of{' '}
|
||||
{event.details.totalTasks}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{event.details.completedTask && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Completed:</span>
|
||||
<span className="italic">
|
||||
"{event.details.completedTask}"
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{event.details.nextTask && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Next:</span>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
Next:
|
||||
</span>
|
||||
<span className="italic">
|
||||
"{event.details.nextTask}"
|
||||
</span>
|
||||
|
|
@ -218,7 +241,9 @@ const AgentActionDisplay = ({
|
|||
)}
|
||||
{event.details.currentSearchFocus && (
|
||||
<div className="flex space-x-1">
|
||||
<span className="font-bold">Search Focus:</span>
|
||||
<span className="font-bold whitespace-nowrap">
|
||||
Search Focus:
|
||||
</span>
|
||||
<span className="italic">
|
||||
"{event.details.currentSearchFocus}"
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -93,7 +93,10 @@ const MessageBox = ({
|
|||
) : (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<h2 className="text-black dark:text-white font-medium text-3xl" onClick={startEditMessage}>
|
||||
<h2
|
||||
className="text-black dark:text-white font-medium text-3xl"
|
||||
onClick={startEditMessage}
|
||||
>
|
||||
{message.content}
|
||||
</h2>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -37,10 +37,12 @@ const MessageBoxLoading = ({ progress }: MessageBoxLoadingProps) => {
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-light-primary dark:bg-dark-primary animate-pulse rounded-lg py-3">
|
||||
<div className="h-2 rounded-full w-full bg-light-secondary dark:bg-dark-secondary" />
|
||||
<div className="h-2 mt-2 rounded-full w-9/12 bg-light-secondary dark:bg-dark-secondary" />
|
||||
<div className="h-2 mt-2 rounded-full w-10/12 bg-light-secondary dark:bg-dark-secondary" />
|
||||
<div className="pl-3 flex items-center justify-start">
|
||||
<div className="flex space-x-1">
|
||||
<div className="w-1.5 h-1.5 bg-black/40 dark:bg-white/40 rounded-full animate-[high-bounce_1s_infinite] [animation-delay:-0.3s]"></div>
|
||||
<div className="w-1.5 h-1.5 bg-black/40 dark:bg-white/40 rounded-full animate-[high-bounce_1s_infinite] [animation-delay:-0.15s]"></div>
|
||||
<div className="w-1.5 h-1.5 bg-black/40 dark:bg-white/40 rounded-full animate-[high-bounce_1s_infinite]"></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,18 +25,32 @@ import next from 'next';
|
|||
|
||||
// Define Zod schemas for structured output
|
||||
const NextActionSchema = z.object({
|
||||
action: z.enum(['good_content', 'need_user_info', 'need_more_info']).describe('The next action to take based on content analysis'),
|
||||
reasoning: z.string().describe('Brief explanation of why this action was chosen')
|
||||
action: z
|
||||
.enum(['good_content', 'need_user_info', 'need_more_info'])
|
||||
.describe('The next action to take based on content analysis'),
|
||||
reasoning: z
|
||||
.string()
|
||||
.describe('Brief explanation of why this action was chosen'),
|
||||
});
|
||||
|
||||
const UserInfoRequestSchema = z.object({
|
||||
question: z.string().describe('A detailed question to ask the user for additional information'),
|
||||
reasoning: z.string().describe('Explanation of why this information is needed')
|
||||
question: z
|
||||
.string()
|
||||
.describe('A detailed question to ask the user for additional information'),
|
||||
reasoning: z
|
||||
.string()
|
||||
.describe('Explanation of why this information is needed'),
|
||||
});
|
||||
|
||||
const SearchRefinementSchema = z.object({
|
||||
question: z.string().describe('A refined search question to gather more specific information'),
|
||||
reasoning: z.string().describe('Explanation of what information is missing and why this search will help')
|
||||
question: z
|
||||
.string()
|
||||
.describe('A refined search question to gather more specific information'),
|
||||
reasoning: z
|
||||
.string()
|
||||
.describe(
|
||||
'Explanation of what information is missing and why this search will help',
|
||||
),
|
||||
});
|
||||
|
||||
export class AnalyzerAgent {
|
||||
|
|
@ -120,9 +134,7 @@ export class AnalyzerAgent {
|
|||
|
||||
console.log('Next action response:', nextActionResponse);
|
||||
|
||||
if (
|
||||
nextActionResponse.action !== 'good_content'
|
||||
) {
|
||||
if (nextActionResponse.action !== 'good_content') {
|
||||
// If we don't have enough information, but we still have available tasks, proceed with the next task
|
||||
|
||||
if (state.tasks && state.tasks.length > 0) {
|
||||
|
|
@ -135,13 +147,14 @@ export class AnalyzerAgent {
|
|||
}
|
||||
}
|
||||
|
||||
if (
|
||||
nextActionResponse.action === 'need_user_info'
|
||||
) {
|
||||
if (nextActionResponse.action === 'need_user_info') {
|
||||
// Use structured output for user info request
|
||||
const userInfoLlm = this.llm.withStructuredOutput(UserInfoRequestSchema, {
|
||||
name: 'request_user_info',
|
||||
});
|
||||
const userInfoLlm = this.llm.withStructuredOutput(
|
||||
UserInfoRequestSchema,
|
||||
{
|
||||
name: 'request_user_info',
|
||||
},
|
||||
);
|
||||
|
||||
const moreUserInfoPrompt = await ChatPromptTemplate.fromTemplate(
|
||||
additionalUserInputPrompt,
|
||||
|
|
@ -193,9 +206,12 @@ export class AnalyzerAgent {
|
|||
|
||||
// If we need more information from the LLM, generate a more specific search query
|
||||
// Use structured output for search refinement
|
||||
const searchRefinementLlm = this.llm.withStructuredOutput(SearchRefinementSchema, {
|
||||
name: 'refine_search',
|
||||
});
|
||||
const searchRefinementLlm = this.llm.withStructuredOutput(
|
||||
SearchRefinementSchema,
|
||||
{
|
||||
name: 'refine_search',
|
||||
},
|
||||
);
|
||||
|
||||
const moreInfoPrompt = await ChatPromptTemplate.fromTemplate(
|
||||
additionalWebSearchPrompt,
|
||||
|
|
@ -268,7 +284,7 @@ export class AnalyzerAgent {
|
|||
type: 'agent_action',
|
||||
data: {
|
||||
action: 'INFORMATION_GATHERING_COMPLETE',
|
||||
message: 'Sufficient information gathered, ready to respond.',
|
||||
message: 'Ready to respond.',
|
||||
details: {
|
||||
documentCount: state.relevantDocuments.length,
|
||||
searchIterations: state.searchInstructionHistory.length,
|
||||
|
|
|
|||
|
|
@ -10,8 +10,16 @@ import { setTemperature } from '../utils/modelUtils';
|
|||
|
||||
// Define Zod schema for structured task breakdown output
|
||||
const TaskBreakdownSchema = z.object({
|
||||
tasks: z.array(z.string()).describe('Array of specific, focused tasks broken down from the original query'),
|
||||
reasoning: z.string().describe('Explanation of how and why the query was broken down into these tasks')
|
||||
tasks: z
|
||||
.array(z.string())
|
||||
.describe(
|
||||
'Array of specific, focused tasks broken down from the original query',
|
||||
),
|
||||
reasoning: z
|
||||
.string()
|
||||
.describe(
|
||||
'Explanation of how and why the query was broken down into these tasks',
|
||||
),
|
||||
});
|
||||
|
||||
type TaskBreakdown = z.infer<typeof TaskBreakdownSchema>;
|
||||
|
|
@ -136,7 +144,9 @@ export class TaskManagerAgent {
|
|||
console.log('Task breakdown response:', taskBreakdownResult);
|
||||
|
||||
// Extract tasks from structured response
|
||||
const taskLines = taskBreakdownResult.tasks.filter((task) => task.trim().length > 0);
|
||||
const taskLines = taskBreakdownResult.tasks.filter(
|
||||
(task) => task.trim().length > 0,
|
||||
);
|
||||
|
||||
if (taskLines.length === 0) {
|
||||
// Fallback: if no tasks found, use the original query
|
||||
|
|
|
|||
|
|
@ -22,8 +22,14 @@ import computeSimilarity from '../utils/computeSimilarity';
|
|||
|
||||
// Define Zod schema for structured search query output
|
||||
const SearchQuerySchema = z.object({
|
||||
searchQuery: z.string().describe('The optimized search query to use for web search'),
|
||||
reasoning: z.string().describe('Explanation of how the search query was optimized for better results')
|
||||
searchQuery: z
|
||||
.string()
|
||||
.describe('The optimized search query to use for web search'),
|
||||
reasoning: z
|
||||
.string()
|
||||
.describe(
|
||||
'Explanation of how the search query was optimized for better results',
|
||||
),
|
||||
});
|
||||
|
||||
type SearchQuery = z.infer<typeof SearchQuerySchema>;
|
||||
|
|
@ -333,7 +339,7 @@ export class WebSearchAgent {
|
|||
type: 'agent_action',
|
||||
data: {
|
||||
action: 'ANALYZING_SOURCE',
|
||||
message: `Analyzing content from: ${result.title || result.url}`,
|
||||
message: `Analyzing and summarizing content from: ${result.title || result.url}`,
|
||||
details: {
|
||||
query: currentTask,
|
||||
sourceUrl: result.url,
|
||||
|
|
|
|||
|
|
@ -18,8 +18,17 @@ export type PreviewContent = {
|
|||
|
||||
// Zod schema for structured preview analysis output
|
||||
const PreviewAnalysisSchema = z.object({
|
||||
isSufficient: z.boolean().describe('Whether the preview content is sufficient to answer the task query'),
|
||||
reason: z.string().nullable().describe('Specific reason why full content analysis is required (only if isSufficient is false)')
|
||||
isSufficient: z
|
||||
.boolean()
|
||||
.describe(
|
||||
'Whether the preview content is sufficient to answer the task query',
|
||||
),
|
||||
reason: z
|
||||
.string()
|
||||
.nullable()
|
||||
.describe(
|
||||
'Specific reason why full content analysis is required (only if isSufficient is false)',
|
||||
),
|
||||
});
|
||||
|
||||
export const analyzePreviewContent = async (
|
||||
|
|
@ -123,9 +132,11 @@ You must return a JSON object with:
|
|||
console.log(
|
||||
`Preview content determined to be insufficient. Reason: ${analysisResult.reason}`,
|
||||
);
|
||||
return {
|
||||
isSufficient: false,
|
||||
reason: analysisResult.reason || 'Preview content insufficient for complete answer'
|
||||
return {
|
||||
isSufficient: false,
|
||||
reason:
|
||||
analysisResult.reason ||
|
||||
'Preview content insufficient for complete answer',
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export const summarizeWebContent = async (
|
|||
console.log(
|
||||
`Summarizing content from URL: ${url} using ${i === 0 ? 'html' : 'text'}`,
|
||||
);
|
||||
|
||||
|
||||
const prompt = `${systemPrompt}You are a web content summarizer, tasked with creating a detailed, accurate summary of content from a webpage.
|
||||
|
||||
# Instructions
|
||||
|
|
@ -82,9 +82,9 @@ ${i === 0 ? content.metadata.html : content.pageContent}`;
|
|||
`LLM response for URL "${url}" indicates it's not relevant (empty or very short response)`,
|
||||
);
|
||||
|
||||
return {
|
||||
document: null,
|
||||
notRelevantReason: 'Content not relevant to query'
|
||||
return {
|
||||
document: null,
|
||||
notRelevantReason: 'Content not relevant to query',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue