草案 代理设计模式 - 探索与发现
本指南演示了如何使用 TypeScript、Next.js 15、LangChain 和 LangGraph 在 Vercel 的无服务器平台上构建自主探索与发现代理。我们将创建能够主动寻找新信息、发现隐藏模式并产生超越预定义解决方案空间的新见解的代理。
心智模型:科学研究实验室
可以将探索代理想象成组建一个虚拟研究实验室。您的代理充当首席科学家(协调者),派生出专门的研究助理(工作代理)来探索不同的假设。LangGraph 提供实验室基础设施(状态管理和工作流),而 LangChain 工具则充当研究设备。探索过程模仿了科学方法:生成假设,通过探索进行测试,评估发现,并根据发现进行迭代。Vercel 的无服务器平台充当可扩展的实验室空间,可以根据研究需求进行扩展或收缩。
基础示例:假设驱动的探索者
1. 核心探索状态管理
// lib/exploration/types.ts
import { BaseMessage } from '@langchain/core/messages';
import { z } from 'zod';
export const ExplorationStateSchema = z.object({
query: z.string(),
currentHypothesis: z.string().optional(),
explorationDepth: z.number().default(0),
discoveredPaths: z.array(z.string()).default([]),
findings: z.array(z.object({
path: z.string(),
content: z.string(),
confidence: z.number(),
timestamp: z.number()
})).default([]),
confidenceThreshold: z.number().default(0.7),
maxDepth: z.number().default(5),
status: z.enum(['exploring', 'evaluating', 'backtracking', 'complete']).default('exploring')
});
export type ExplorationState = z.infer<typeof ExplorationStateSchema>;
export interface ExplorationNode {
id: string;
hypothesis: string;
score: number;
children: ExplorationNode[];
visited: boolean;
metadata: Record<string, any>;
}
定义了探索代理的核心状态结构,包括假设跟踪、已发现路径、置信度分数和基于树的探索节点。
2. 基础探索代理
// lib/exploration/basic-explorer.ts
import { StateGraph, MessagesAnnotation } from '@langchain/langgraph';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { TavilySearchResults } from '@langchain/community/tools/tavily_search';
import { HumanMessage, AIMessage } from '@langchain/core/messages';
import { pull } from 'langchain/hub';
import { filter, map, sortBy } from 'es-toolkit';
interface BasicExplorationState {
messages: BaseMessage[];
hypothesis: string;
explorationCount: number;
discoveries: string[];
}
export async function createBasicExplorer() {
const model = new ChatGoogleGenerativeAI({
temperature: 0.7,
modelName: 'gemini-2.5-flash'
});
const searchTool = new TavilySearchResults({ maxResults: 3 });
// 节点:生成假设
async function generateHypothesis(state: BasicExplorationState) {
const prompt = await pull('exploration/hypothesis-generator');
const response = await model.invoke([
new HumanMessage(`Based on: ${state.messages.slice(-1)[0].content}\n Generate a testable hypothesis for exploration.`)
]);
return {
hypothesis: response.content,
messages: [...state.messages, response]
};
}
// 节点:探索假设
async function exploreHypothesis(state: BasicExplorationState) {
const searchResults = await searchTool.invoke(state.hypothesis);
const parsedResults = JSON.parse(searchResults);
const findings = map(
filter(parsedResults, (r: any) => r.score > 0.5),
(result: any) => result.content
);
return {
discoveries: [...state.discoveries, ...findings],
explorationCount: state.explorationCount + 1,
messages: [...state.messages, new AIMessage(`Explored: ${findings.length} findings`)]
};
}
// 节点:评估发现
async function evaluateFindings(state: BasicExplorationState) {
const sortedDiscoveries = sortBy(
state.discoveries,
(d: string) => d.length
);
const evaluation = await model.invoke([
new HumanMessage(`Evaluate these discoveries: ${sortedDiscoveries.join('\n')}\n Should we continue exploring or have we found sufficient insights?`)
]);
return {
messages: [...state.messages, evaluation]
};
}
// 条件边:是否继续探索?
function shouldContinue(state: BasicExplorationState) {
if (state.explorationCount >= 5) return 'end';
if (state.discoveries.length > 10) return 'evaluate';
return 'explore';
}
// 构建图
const workflow = new StateGraph<BasicExplorationState>({
channels: {
messages: { value: (x: BaseMessage[], y: BaseMessage[]) => [...x, ...y], default: () => [] },
hypothesis: { value: (x, y) => y || x, default: () => '' },
explorationCount: { value: (x, y) => y || x, default: () => 0 },
discoveries: { value: (x: string[], y: string[]) => [...x, ...y], default: () => [] }
}
});
workflow.addNode('generate', generateHypothesis);
workflow.addNode('explore', exploreHypothesis);
workflow.addNode('evaluate', evaluateFindings);
workflow.addEdge('__start__', 'generate');
workflow.addEdge('generate', 'explore');
workflow.addConditionalEdges('explore', shouldContinue, {
'explore': 'generate',
'evaluate': 'evaluate',
'end': '__end__'
});
workflow.addEdge('evaluate', '__end__');
return workflow.compile();
}
创建一个基础的探索代理,该代理可以生成假设,使用搜索工具进行探索,并在一个迭代循环中评估发现。
3. 用于探索的 API 路由
// app/api/explore/route.ts
import { createBasicExplorer } from '@/lib/exploration/basic-explorer';
import { HumanMessage } from '@langchain/core/messages';
import { NextResponse } from 'next/server';
export const runtime = 'nodejs';
export const maxDuration = 300;
export async function POST(req: Request) {
try {
const { query, sessionId } = await req.json();
const explorer = await createBasicExplorer();
const encoder = new TextEncoder();
const stream = new TransformStream();
const writer = stream.writable.getWriter();
// 在后台运行探索
(async () => {
try {
const eventStream = await explorer.stream({
messages: [new HumanMessage(query)],
hypothesis: '',
explorationCount: 0,
discoveries: []
});
for await (const event of eventStream) {
const update = {
type: 'exploration_update',
hypothesis: event.hypothesis,
discoveries: event.discoveries?.length || 0,
status: event.explorationCount < 5 ? 'exploring' : 'complete'
};
await writer.write(
encoder.encode(`data: ${JSON.stringify(update)}\n\n`)
);
}
} catch (error) {
console.error('Exploration error:', error);
} finally {
await writer.close();
}
})();
return new Response(stream.readable, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
} catch (error) {
console.error('API error:', error);
return NextResponse.json(
{ error: 'Failed to start exploration' },
{ status: 500 }
);
}
}
API 端点使用 Server-Sent Events (SSE) 实时流式传输探索进度,以提供响应式的用户体验。
4. 前端探索界面
// components/ExplorationInterface.tsx
'use client';
import { useState, useCallback } from 'react';
import { useMutation } from '@tanstack/react-query';
import { debounce } from 'es-toolkit';
interface ExplorationUpdate {
type: string;
hypothesis?: string;
discoveries?: number;
status?: string;
}
export default function ExplorationInterface() {
const [query, setQuery] = useState('');
const [updates, setUpdates] = useState<ExplorationUpdate[]>([]);
const [isExploring, setIsExploring] = useState(false);
const startExploration = useMutation({
mutationFn: async (explorationQuery: string) => {
const response = await fetch('/api/explore', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: explorationQuery })
});
if (!response.ok) throw new Error('Exploration failed');
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (reader) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
setUpdates(prev => [...prev, data]);
}
}
}
},
onSuccess: () => {
setIsExploring(false);
},
onError: (error) => {
console.error('Exploration error:', error);
setIsExploring(false);
}
});
const handleExplore = useCallback(
debounce(() => {
if (query.trim()) {
setIsExploring(true);
setUpdates([]);
startExploration.mutate(query);
}
}, 500),
[query]
);
return (
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h2 className="card-title">探索与发现代理</h2>
<div className="form-control">
<label className="label">
<span className="label-text">您想探索什么?</span>
</label>
<textarea
className="textarea textarea-bordered h-24"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="输入一个主题或问题进行深度探索..."
/>
</div>
<div className="card-actions justify-end">
<button
className="btn btn-primary"
onClick={handleExplore}
disabled={isExploring || !query.trim()}
>
{isExploring ? (
<>
<span className="loading loading-spinner"></span>
探索中...
</>
) : '开始探索'}
</button>
</div>
{updates.length > 0 && (
<div className="mt-4">
<h3 className="font-semibold mb-2">探索进度:</h3>
<div className="space-y-2 max-h-64 overflow-y-auto">
{updates.map((update, idx) => (
<div key={idx} className="alert alert-info">
<div>
<span className="font-semibold">假设:</span> {update.hypothesis}
<br />
<span className="font-semibold">发现:</span> {update.discoveries}
<br />
<span className="badge badge-sm">{update.status}</span>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
);
}
提供探索过程实时可视化的 React 组件,具有假设生成和发现跟踪功能。
高级示例:多代理科学发现系统
1. 蒙特卡洛树搜索探索者
// lib/exploration/mcts-explorer.ts
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { mean, sum, maxBy, sample } from 'es-toolkit';
import { v4 as uuidv4 } from 'uuid';
interface MCTSNode {
id: string;
state: string;
value: number;
visits: number;
parent?: MCTSNode;
children: MCTSNode[];
untried_actions: string[];
}
export class MonteCarloTreeSearchExplorer {
private model: ChatGoogleGenerativeAI;
private explorationConstant: number = Math.sqrt(2);
private maxIterations: number = 100;
constructor() {
this.model = new ChatGoogleGenerativeAI({
temperature: 0.9,
modelName: 'gemini-2.5-pro'
});
}
async explore(problem: string, iterations: number = 100): Promise<any> {
this.maxIterations = iterations;
const root = this.createNode(problem);
for (let i = 0; i < this.maxIterations; i++) {
// 选择
const selectedNode = await this.select(root);
// 扩展
const expandedNode = await this.expand(selectedNode);
// 模拟(自我优化)
const reward = await this.simulate(expandedNode);
// 反向传播
await this.backpropagate(expandedNode, reward);
}
return this.getBestPath(root);
}
private createNode(state: string, parent?: MCTSNode): MCTSNode {
return {
id: uuidv4(),
state,
value: 0,
visits: 0,
parent,
children: [],
untried_actions: []
};
}
private async select(node: MCTSNode): Promise<MCTSNode> {
while (node.children.length > 0) {
const ucbValues = node.children.map(child => this.calculateUCB(child));
const maxIndex = ucbValues.indexOf(Math.max(...ucbValues));
node = node.children[maxIndex];
}
return node;
}
private calculateUCB(node: MCTSNode): number {
if (node.visits === 0) return Infinity;
const exploitation = node.value / node.visits;
const exploration = this.explorationConstant *
Math.sqrt(Math.log(node.parent!.visits) / node.visits);
return exploitation + exploration;
}
private async expand(node: MCTSNode): Promise<MCTSNode> {
// 使用 LLM 生成可能的动作
const response = await this.model.invoke([{
role: 'system',
content: 'You are exploring solution spaces. Generate 3 diverse next steps.'
}, {
role: 'user',
content: `Current state: ${node.state}\nGenerate next exploration steps as JSON array.`
}]);
try {
const actions = JSON.parse(response.content as string);
node.untried_actions = actions;
if (actions.length > 0) {
const action = sample(actions);
const childNode = this.createNode(action, node);
node.children.push(childNode);
return childNode;
}
} catch (e) {
console.error('Failed to parse actions:', e);
}
return node;
}
private async simulate(node: MCTSNode): Promise<number> {
// 自我优化:使用 LLM 评估和优化当前路径
const response = await this.model.invoke([{
role: 'system',
content: 'Evaluate this exploration path and suggest improvements. Rate quality 0-1.'
}, {
role: 'user',
content: `Path: ${this.getPath(node).join(' -> ')}\nEvaluate and score.`
}]);
// 从响应中提取分数
const scoreMatch = response.content.toString().match(/\d\.\d+/);
return scoreMatch ? parseFloat(scoreMatch[0]) : 0.5;
}
private async backpropagate(node: MCTSNode, reward: number) {
let current: MCTSNode | undefined = node;
while (current) {
current.visits++;
current.value += reward;
current = current.parent;
}
}
private getPath(node: MCTSNode): string[] {
const path: string[] = [];
let current: MCTSNode | undefined = node;
while (current) {
path.unshift(current.state);
current = current.parent;
}
return path;
}
private getBestPath(root: MCTSNode): any {
const bestChild = maxBy(root.children, child => child.value / child.visits);
if (!bestChild) return { path: [root.state], score: 0 };
return {
path: this.getPath(bestChild),
score: bestChild.value / bestChild.visits,
explorations: this.countTotalNodes(root)
};
}
private countTotalNodes(node: MCTSNode): number {
return 1 + sum(node.children.map(child => this.countTotalNodes(child)));
}
}
为智能探索实现蒙特卡洛树搜索,具有基于 UCB 的选择、LLM 驱动的扩展和自我优化功能。
2. 多代理研究实验室
// lib/exploration/research-laboratory.ts
import { StateGraph, MessagesAnnotation } from '@langchain/langgraph';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';
import { groupBy, flatten, uniqBy } from 'es-toolkit';
import { MonteCarloTreeSearchExplorer } from './mcts-explorer';
interface ResearchState {
messages: BaseMessage[];
research_topic: string;
hypotheses: Array<{id: string; content: string; score: number}>;
literature_review: string;
experiments: Array<{id: string; design: string; results?: string}>;
synthesis: string;
phase: 'planning' | 'literature' | 'hypothesis' | 'experimentation' | 'synthesis';
}
export class ResearchLaboratory {
private leadModel: ChatGoogleGenerativeAI;
private workerModel: ChatGoogleGenerativeAI;
private mctsExplorer: MonteCarloTreeSearchExplorer;
constructor() {
this.leadModel = new ChatGoogleGenerativeAI({
temperature: 0.3,
modelName: 'gemini-2.5-pro'
});
this.workerModel = new ChatGoogleGenerativeAI({
temperature: 0.7,
modelName: 'gemini-2.5-flash'
});
this.mctsExplorer = new MonteCarloTreeSearchExplorer();
}
async createResearchWorkflow() {
const workflow = new StateGraph<ResearchState>({
channels: {
messages: {
value: (x: BaseMessage[], y: BaseMessage[]) => [...x, ...y],
default: () => []
},
research_topic: {
value: (x, y) => y || x,
default: () => ''
},
hypotheses: {
value: (x, y) => y || x,
default: () => []
},
literature_review: {
value: (x, y) => y || x,
default: () => ''
},
experiments: {
value: (x, y) => y || x,
default: () => []
},
synthesis: {
value: (x, y) => y || x,
default: () => ''
},
phase: {
value: (x, y) => y || x,
default: () => 'planning'
}
}
});
// 规划代理
workflow.addNode('planner', async (state) => {
const plan = await this.leadModel.invoke([
new HumanMessage(`Create a research plan for: ${state.research_topic}\n Include: 1) Key areas to investigate 2) Methodology 3) Success metrics`)
]);
return {
messages: [...state.messages, plan],
phase: 'literature' as const
};
});
// 文献综述代理
workflow.addNode('literature_reviewer', async (state) => {
// 模拟多个代理并行进行文献综述
const reviewPromises = Array.from({ length: 3 }, async (_, i) => {
const review = await this.workerModel.invoke([
new HumanMessage(`Review literature on ${state.research_topic}\n Focus area ${i + 1}: ${['theoretical foundations', 'recent advances', 'open problems'][i]}`)
]);
return review.content;
});
const reviews = await Promise.all(reviewPromises);
const combinedReview = reviews.join('\n\n');
return {
literature_review: combinedReview,
messages: [...state.messages, new AIMessage(combinedReview)],
phase: 'hypothesis' as const
};
});
// 假设生成代理(使用 MCTS)
workflow.addNode('hypothesis_generator', async (state) => {
const explorationResult = await this.mctsExplorer.explore(
`Generate hypotheses for: ${state.research_topic}\nBased on: ${state.literature_review}`,
50
);
const hypotheses = explorationResult.path.map((h: string, idx: number) => ({
id: `hyp_${idx}`,
content: h,
score: Math.random() * (1 - 0.5) + 0.5 // 模拟评分
}));
return {
hypotheses: hypotheses,
messages: [...state.messages, new AIMessage(`Generated ${hypotheses.length} hypotheses`)],
phase: 'experimentation' as const
};
});
// 实验设计代理
workflow.addNode('experimenter', async (state) => {
const topHypotheses = state.hypotheses
.sort((a, b) => b.score - a.score)
.slice(0, 3);
const experiments = await Promise.all(
topHypotheses.map(async (hyp) => {
const design = await this.workerModel.invoke([
new HumanMessage(`Design an experiment to test: ${hyp.content}`)
]);
return {
id: hyp.id,
design: design.content as string,
results: `Simulated results for ${hyp.id}`
};
})
);
return {
experiments: experiments,
messages: [...state.messages, new AIMessage(`Designed ${experiments.length} experiments`)],
phase: 'synthesis' as const
};
});
// 综合代理
workflow.addNode('synthesizer', async (state) => {
const synthesis = await this.leadModel.invoke([
new HumanMessage(`Synthesize research findings:\n Topic: ${state.research_topic}\n Literature: ${state.literature_review}\n Hypotheses: ${JSON.stringify(state.hypotheses)}\n Experiments: ${JSON.stringify(state.experiments)}\n \n Provide comprehensive insights and conclusions.`)
]);
return {
synthesis: synthesis.content as string,
messages: [...state.messages, synthesis]
};
});
// 定义工作流边
workflow.addEdge('__start__', 'planner');
workflow.addEdge('planner', 'literature_reviewer');
workflow.addEdge('literature_reviewer', 'hypothesis_generator');
workflow.addEdge('hypothesis_generator', 'experimenter');
workflow.addEdge('experimenter', 'synthesizer');
workflow.addEdge('synthesizer', '__end__');
return workflow.compile();
}
}
实现一个完整的多代理研究实验室,包含专门用于规划、文献综述、使用 MCTS 生成假设、实验和综合的代理。
3. 具有状态持久性的无服务器探索
// lib/exploration/serverless-explorer.ts
import { kv } from '@vercel/kv';
import { Queue } from 'bullmq';
import { chunk, throttle } from 'es-toolkit';
interface ExplorationChunk {
id: string;
sessionId: string;
chunkIndex: number;
totalChunks: number;
state: any;
timestamp: number;
}
export class ServerlessExplorer {
private maxExecutionTime = 777; // 秒(带安全缓冲)
private checkpointInterval = 60; // 秒
async executeWithCheckpoints(
explorationFn: () => Promise<any>,
sessionId: string
) {
const startTime = Date.now();
const checkpointKey = `exploration:${sessionId}`;
// 尝试恢复先前的状态
const previousState = await kv.get<ExplorationChunk>(checkpointKey);
let currentState = previousState?.state || {};
const executeWithTimeout = async () => {
const elapsedSeconds = (Date.now() - startTime) / 1000;
if (elapsedSeconds >= this.maxExecutionTime - 30) {
// 保存状态并安排继续执行
await this.saveCheckpoint(sessionId, currentState);
await this.scheduleContinuation(sessionId);
return { status: 'paused', state: currentState };
}
// 执行探索块
const result = await explorationFn();
currentState = { ...currentState, ...result };
// 定期检查点
if (elapsedSeconds % this.checkpointInterval < 1) {
await this.saveCheckpoint(sessionId, currentState);
}
return { status: 'continuing', state: currentState };
};
// 节流执行以防止速率限制
const throttledExecute = throttle(executeWithTimeout, 1000);
let status = 'continuing';
while (status === 'continuing') {
const result = await throttledExecute();
status = result.status;
currentState = result.state;
}
return currentState;
}
private async saveCheckpoint(sessionId: string, state: any) {
const checkpoint: ExplorationChunk = {
id: `checkpoint_${Date.now()}`,
sessionId,
chunkIndex: state.chunkIndex || 0,
totalChunks: state.totalChunks || 1,
state,
timestamp: Date.now()
};
await kv.set(
`exploration:${sessionId}`,
checkpoint,
{ ex: 3600 } // 1 小时过期
);
}
private async scheduleContinuation(sessionId: string) {
// 使用 Inngest 或类似工具进行调度
await fetch('/api/schedule', {
method: 'POST',
body: JSON.stringify({
event: 'exploration.continue',
data: { sessionId },
delay: '5s'
})
});
}
}
// 用于分块探索的 API 路由
export async function POST(req: Request) {
const { sessionId, query } = await req.json();
const explorer = new ServerlessExplorer();
const explorationTasks = chunk(
Array.from({ length: 20 }, (_, i) => i),
5
);
const result = await explorer.executeWithCheckpoints(
async () => {
// 执行一个探索块
const tasks = explorationTasks.shift();
if (!tasks) return { complete: true };
const results = await Promise.all(
tasks.map(async (taskId) => {
// 模拟探索任务
return { taskId, result: `Discovery ${taskId}` };
})
);
return {
chunkIndex: (explorationTasks.length || 0) + 1,
discoveries: results
};
},
sessionId
);
return new Response(JSON.stringify(result), {
headers: { 'Content-Type': 'application/json' }
});
}
为适应无服务器环境的探索实现,具有自动检查点、状态持久化和任务续行功能,适用于长时间运行的探索。
4. 向量内存集成
// lib/exploration/memory-system.ts
import { Pinecone } from '@pinecone-database/pinecone';
import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai';
import { Document } from '@langchain/core/documents';
import { groupBy, sortBy, take } from 'es-toolkit';
export class ExplorationMemorySystem {
private pinecone: Pinecone;
private embeddings: GoogleGenerativeAIEmbeddings;
private indexName = 'exploration-memory';
constructor() {
this.pinecone = new Pinecone({
apiKey: process.env.PINECONE_API_KEY!
});
this.embeddings = new GoogleGenerativeAIEmbeddings({
modelName: 'embedding-001'
});
}
async storeDiscovery(
content: string,
metadata: {
sessionId: string;
hypothesis: string;
confidence: number;
timestamp: number;
explorationPath: string[];
}
) {
const embedding = await this.embeddings.embedQuery(content);
const index = this.pinecone.index(this.indexName);
await index.upsert([{
id: `discovery_${metadata.sessionId}_${metadata.timestamp}`,
values: embedding,
metadata: {
...metadata,
content: content.slice(0, 1000) // 存储预览
}
}]);
}
async querySemanticMemory(
query: string,
filters?: Record<string, any>
): Promise<Document[]> {
const queryEmbedding = await this.embeddings.embedQuery(query);
const index = this.pinecone.index(this.indexName);
const results = await index.query({
vector: queryEmbedding,
topK: 20,
includeMetadata: true,
filter: filters
});
// 按假设分组并从每组中取最优
const grouped = groupBy(
results.matches,
(match: any) => match.metadata.hypothesis
);
const diverseResults = Object.values(grouped).map(group => {
const sorted = sortBy(group, (m: any) => -m.score);
return sorted[0];
});
return take(diverseResults, 10).map(match =>
new Document({
pageContent: match.metadata.content,
metadata: match.metadata
})
);
}
async getExplorationPattern(sessionId: string) {
const index = this.pinecone.index(this.indexName);
const results = await index.query({
vector: new Array(768).fill(0), // Gemini 嵌入的虚拟向量
topK: 100,
includeMetadata: true,
filter: { sessionId }
});
// 分析探索模式
const pathFrequency = new Map<string, number>();
results.matches.forEach(match => {
const path = match.metadata.explorationPath?.join(' -> ') || '';
pathFrequency.set(path, (pathFrequency.get(path) || 0) + 1);
});
return {
totalDiscoveries: results.matches.length,
avgConfidence: results.matches.reduce(
(sum, m) => sum + (m.metadata.confidence || 0), 0
) / results.matches.length,
mostFrequentPaths: Array.from(pathFrequency.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
};
}
}
使用 Pinecone 向量数据库实现语义内存存储和检索,用于长期探索模式学习。
5. 具有实时可视化的高级探索 UI
// components/AdvancedExplorationUI.tsx
'use client';
import { useState, useEffect } from 'react';
import { useQuery, useMutation } from '@tanstack/react-query';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { debounce } from 'es-toolkit';
interface ExplorationMetrics {
timestamp: number;
depth: number;
discoveries: number;
confidence: number;
}
export default function AdvancedExplorationUI() {
const [topic, setTopic] = useState('');
const [metrics, setMetrics] = useState<ExplorationMetrics[]>([]);
const [currentPhase, setCurrentPhase] = useState('idle');
const [hypotheses, setHypotheses] = useState<Array<{id: string; content: string; score: number}>>([]);
const startResearch = useMutation({
mutationFn: async (researchTopic: string) => {
const response = await fetch('/api/research-lab', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ topic: researchTopic })
});
if (!response.ok) throw new Error('Research failed');
const reader = response.body?.getReader();
const decoder = new TextDecoder();
while (reader) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
// 更新阶段
if (data.phase) setCurrentPhase(data.phase);
// 更新假设
if (data.hypotheses) setHypotheses(data.hypotheses);
// 更新指标
if (data.metrics) {
setMetrics(prev => [...prev, {
timestamp: Date.now(),
depth: data.metrics.depth,
discoveries: data.metrics.discoveries,
confidence: data.metrics.confidence
}]);
}
}
}
}
}
});
const memoryStats = useQuery({
queryKey: ['memory-stats', topic],
queryFn: async () => {
const response = await fetch(`/api/memory-stats?topic=${encodeURIComponent(topic)}`);
return response.json();
},
enabled: !!topic,
refetchInterval: 5000
});
return (
<div className="min-h-screen bg-base-200 p-4">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-8">
<h1 className="text-5xl font-bold mb-2">研究实验室</h1>
<p className="text-xl">多代理科学发现系统</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
{/* 控制面板 */}
<div className="lg:col-span-1">
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h2 className="card-title">研究控制</h2>
<div className="form-control">
<label className="label">
<span className="label-text">研究主题</span>
</label>
<input
type="text"
className="input input-bordered"
value={topic}
onChange={(e) => setTopic(e.target.value)}
placeholder="输入研究主题..."
/>
</div>
<button
className="btn btn-primary mt-4"
onClick={() => startResearch.mutate(topic)}
disabled={!topic || startResearch.isPending}
>
{startResearch.isPending ? (
<>
<span className="loading loading-spinner"></span>
研究中...
</>
) : '开始研究'}
</button>
<div className="divider"></div>
<div className="stats stats-vertical shadow">
<div className="stat">
<div className="stat-title">当前阶段</div>
<div className="stat-value text-primary">{currentPhase}</div>
</div>
<div className="stat">
<div className="stat-title">假设</div>
<div className="stat-value">{hypotheses.length}</div>
</div>
<div className="stat">
<div className="stat-title">内存条目</div>
<div className="stat-value">{memoryStats.data?.totalEntries || 0}</div>
</div>
</div>
</div>
</div>
</div>
{/* 可视化面板 */}
<div className="lg:col-span-2">
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h2 className="card-title">探索指标</h2>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={metrics}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="timestamp" />
<YAxis />
<Tooltip />
<Line
type="monotone"
dataKey="confidence"
stroke="#8884d8"
name="置信度"
/>
<Line
type="monotone"
dataKey="discoveries"
stroke="#82ca9d"
name="发现"
/>
<Line
type="monotone"
dataKey="depth"
stroke="#ffc658"
name="深度"
/>
</LineChart>
</ResponsiveContainer>
<div className="divider"></div>
{/* 假设列表 */}
<h3 className="font-semibold mb-2">生成的假设</h3>
<div className="space-y-2 max-h-64 overflow-y-auto">
{hypotheses.map((hyp) => (
<div key={hyp.id} className="alert">
<div className="flex-1">
<p className="font-semibold">{hyp.content}</p>
<div className="flex items-center gap-2 mt-1">
<span className="badge badge-sm">分数: {hyp.score.toFixed(2)}</span>
<progress
className="progress progress-primary w-24"
value={hyp.score}
max="1"
></progress>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
具有实时指标可视化、假设跟踪和内存统计的高级 UI 组件,用于全面的探索监控。
结论
此实现演示了可在生产环境中使用的探索与发现代理,这些代理使用 TypeScript、LangChain 和 LangGraph 在 Vercel 的无服务器平台上自主导航复杂的问题空间。所示模式使代理能够生成假设,使用蒙特卡洛树搜索探索解决方案空间,协调多代理研究团队,并维护语义内存以进行持续学习。关键的架构决策包括使用 Server-Sent Events 进行实时流式传输,为在无服务器约束内长时间运行的探索实现检查点,利用向量数据库进行模式识别,以及使用 es-toolkit 进行高效的数据操作。这些探索代理将 AI 系统从被动的工具转变为主动的研究伙伴,能够发现新的见解并扩展自身的能力。