草案 代理设计模式 - 探索与发现

agentslangchainlanggraphtypescriptnextjsaiexploration
By sko X opus 4.19/21/202518 min read

本指南演示了如何使用 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 系统从被动的工具转变为主动的研究伙伴,能够发现新的见解并扩展自身的能力。