首页
/ ADK.js LlmAgent深度定制指南:从请求拦截到业务落地

ADK.js LlmAgent深度定制指南:从请求拦截到业务落地

2026-03-07 05:55:58作者:凤尚柏Louis

核心概念篇:揭开LlmAgent的黑箱

要点概览

  • LlmAgent是ADK.js的核心交互组件,负责管理LLM通信全生命周期
  • 处理器(Processors)用于系统性修改请求/响应流,钩子(Callbacks)用于插入离散逻辑
  • 自定义扩展需遵循"最小侵入"原则,优先复用内置组件

如何理解LlmAgent的工作原理?

LlmAgent作为AI代理的"大脑",协调着与大型语言模型(LLM)的所有交互。其核心工作流包含四个阶段:

  1. 请求预处理:将用户输入转换为LLM可理解的格式
  2. 模型调用:处理与LLM的网络通信和错误恢复
  3. 响应后处理:解析LLM返回结果并提取结构化信息
  4. 工具协调:根据LLM决策调用外部工具并处理结果

这种模块化设计允许开发者在不修改核心代码的情况下,通过处理器和钩子实现深度定制。

处理器与钩子:两种扩展方式的对比

特性 处理器(Processors) 钩子(Callbacks)
用途 系统性修改数据流 离散事件响应
实现复杂度 中高
执行时机 固定流程节点 特定事件触发
典型应用 请求格式化、内容转换 日志记录、权限检查
返回值 生成器(可产生事件流) 可修改上下文数据

场景化实践篇:从零开始定制代理行为

要点概览

  • 请求处理器可实现从简单修改到复杂逻辑的全范围定制
  • 钩子系统适合实现横切关注点如日志、监控和安全检查
  • 所有自定义组件需通过ADK.js提供的接口进行类型约束

如何拦截并修改LLM请求?

当需要系统性修改发送给LLM的请求时,自定义请求处理器是最佳选择。以下是一个实现多语言支持的请求处理器:

import { BaseLlmRequestProcessor, InvocationContext, LlmRequest } from '../core/src/agents/base_llm_processor.ts';

// 多语言支持请求处理器
class LocalizationRequestProcessor extends BaseLlmRequestProcessor {
  // 构造函数接收支持的语言列表
  constructor(private supportedLanguages: string[] = ['en', 'zh', 'es']) {
    super();
  }

  async *runAsync(
    invocationContext: InvocationContext,
    llmRequest: LlmRequest
  ): AsyncGenerator<Event, void, void> {
    // 1. 从上下文中获取用户语言偏好
    const userLang = invocationContext.session.get('preferredLanguage') || 'en';
    
    // 2. 验证语言是否支持
    const targetLang = this.supportedLanguages.includes(userLang) ? userLang : 'en';
    
    // 3. 添加语言指令到系统消息
    llmRequest.contents.unshift({
      role: 'system',
      parts: [{ 
        text: `请用${this.getLanguageName(targetLang)}回答问题,保持专业术语准确但表达通俗。` 
      }]
    });
    
    // 4. 生成处理事件
    yield createEvent({
      invocationId: invocationContext.invocationId,
      type: 'localization_applied',
      content: { targetLanguage: targetLang }
    });
    
    // 5. 将控制权传递给下一个处理器
    return yield* this.nextProcessor.runAsync(invocationContext, llmRequest);
  }
  
  // 辅助方法:获取语言名称
  private getLanguageName(code: string): string {
    const names = { 'en': '英语', 'zh': '中文', 'es': '西班牙语' };
    return names[code] || code;
  }
}

注册处理器时需注意顺序,基础处理器应先于自定义处理器执行:

const agent = new LlmAgent({
  model: 'gemini-pro',
  requestProcessors: [
    BASIC_LLM_REQUEST_PROCESSOR,  // 基础配置处理器
    IDENTITY_LLM_REQUEST_PROCESSOR, // 身份信息处理器
    new LocalizationRequestProcessor(['en', 'zh', 'ja']), // 自定义多语言处理器
    CONTENT_REQUEST_PROCESSOR      // 内容格式化处理器
  ]
});

如何在关键节点插入自定义逻辑?

钩子机制允许在不创建完整处理器的情况下,在特定事件发生时执行自定义逻辑。以下是一个实现请求限流的beforeModel钩子:

// 请求频率限制钩子
const rateLimitHook = async ({ context, request }: BeforeModelCallbackArgs) => {
  // 1. 获取当前用户ID
  const userId = context.session.get('userId');
  
  // 2. 检查请求频率(伪代码)
  const requestCount = await rateLimitService.getCount(userId, 'hour');
  
  // 3. 如果超过限制,返回错误响应
  if (requestCount > 100) {
    return {
      content: { 
        parts: [{ 
          text: '抱歉,您的请求频率已超出限制,请1小时后再试。' 
        }] 
      },
      isFinal: true // 标记为最终响应,不再调用LLM
    };
  }
  
  // 4. 记录请求(伪代码)
  await rateLimitService.incrementCount(userId);
  
  // 5. 返回undefined继续正常流程
  return undefined;
};

// 注册钩子
const agent = new LlmAgent({
  // 其他配置...
  beforeModelCallback: rateLimitHook
});

钩子可以是单个函数或函数数组,当使用数组时会按顺序执行:

// 组合多个钩子
agent.configure({
  afterToolCallback: [
    logToolUsage,       // 记录工具使用
    validateToolOutput, // 验证输出格式
    enhanceToolResult   // 增强结果数据
  ]
});

真实场景应用篇:行业落地案例解析

要点概览

  • 金融领域可利用定制代理实现智能风控和合规检查
  • 医疗场景中通过响应处理确保信息准确性和隐私保护
  • 教育行业可构建个性化学习助手,动态调整教学策略

金融风控代理:实时交易监控系统

某大型银行使用ADK.js构建了实时交易监控代理,通过自定义处理器实现风险控制:

// 交易风险评估处理器
class TransactionRiskProcessor extends BaseLlmRequestProcessor {
  async *runAsync(invocationContext: InvocationContext, llmRequest: LlmRequest) {
    // 1. 从上下文中提取交易数据
    const transaction = invocationContext.get('transactionData');
    
    // 2. 添加风险评估指令
    llmRequest.contents.push({
      role: 'system',
      parts: [{ 
        text: `分析以下交易是否存在欺诈风险,重点关注:
              1. 金额是否超出用户历史交易范围
              2. 交易地点是否与常用地区不符
              3. 交易时间是否在非活跃时段
              请返回风险评分(0-100)和关键风险点。` 
      }]
    });
    
    // 3. 添加交易数据作为用户消息
    llmRequest.contents.push({
      role: 'user',
      parts: [{ text: JSON.stringify(transaction, null, 2) }]
    });
    
    return yield* this.nextProcessor.runAsync(invocationContext, llmRequest);
  }
}

// 风险决策钩子
const riskDecisionHook = async ({ context, response }: AfterModelCallbackArgs) => {
  // 解析LLM返回的风险评分
  const riskScore = extractRiskScore(response.content.parts[0].text);
  
  // 根据评分执行不同操作
  if (riskScore > 70) {
    // 高风险:触发人工审核
    context.session.set('status', 'pending_review');
    return {
      ...response,
      content: { parts: [{ text: '交易已标记为高风险,正在等待人工审核。' }] }
    };
  } else if (riskScore > 40) {
    // 中风险:要求额外验证
    context.session.set('status', 'requires_verification');
    return {
      ...response,
      content: { parts: [{ text: '请完成手机验证码验证以继续交易。' }] }
    };
  }
  
  // 低风险:正常处理
  context.session.set('status', 'approved');
  return response;
};

医疗信息处理:患者数据保护代理

某医疗机构实现了保护患者隐私的医疗信息处理代理:

// 患者数据脱敏处理器
class MedicalDataAnonymizer extends BaseLlmRequestProcessor {
  async *runAsync(invocationContext: InvocationContext, llmRequest: LlmRequest) {
    // 1. 识别并替换患者标识符
    for (const content of llmRequest.contents) {
      if (content.role === 'user' && content.parts) {
        for (const part of content.parts) {
          if (typeof part.text === 'string') {
            // 脱敏处理:替换姓名、身份证等敏感信息
            part.text = part.text
              .replace(/患者姓名:\w+/g, '患者姓名:[已脱敏]')
              .replace(/身份证号:\d+/g, '身份证号:[已脱敏]')
              .replace(/病历号:\w+/g, '病历号:[已脱敏]');
          }
        }
      }
    }
    
    // 2. 添加隐私保护指令
    llmRequest.contents.unshift({
      role: 'system',
      parts: [{ 
        text: '作为医疗信息处理助手,你必须遵守HIPAA隐私法规,不得在响应中包含任何患者身份信息。' 
      }]
    });
    
    return yield* this.nextProcessor.runAsync(invocationContext, llmRequest);
  }
}

教育个性化学习:自适应教学代理

某在线教育平台实现了根据学生水平动态调整内容的学习代理:

// 学习内容适配处理器
class AdaptiveLearningProcessor extends BaseLlmRequestProcessor {
  async *runAsync(invocationContext: InvocationContext, llmRequest: LlmRequest) {
    // 1. 获取学生学习数据
    const studentData = invocationContext.session.get('studentProfile');
    
    // 2. 确定难度级别
    const difficulty = this.determineDifficulty(studentData);
    
    // 3. 调整教学内容难度
    llmRequest.contents.push({
      role: 'system',
      parts: [{ 
        text: `根据学生水平调整教学内容:
              - 当前水平: ${difficulty}
              - 解释复杂度: ${this.getExplanationComplexity(difficulty)}
              - 示例数量: ${this.getExampleCount(difficulty)}
              - 问题难度: ${this.getQuestionDifficulty(difficulty)}` 
      }]
    });
    
    return yield* this.nextProcessor.runAsync(invocationContext, llmRequest);
  }
  
  // 根据学生数据确定难度级别
  private determineDifficulty(studentData: StudentProfile): 'beginner' | 'intermediate' | 'advanced' {
    // 基于历史表现的逻辑实现...
    return studentData.averageScore > 85 ? 'advanced' : 
           studentData.averageScore > 65 ? 'intermediate' : 'beginner';
  }
  
  // 根据难度调整解释复杂度
  private getExplanationComplexity(difficulty: string): string {
    const levels = {
      beginner: '使用简单术语和类比,避免专业词汇',
      intermediate: '使用适当专业术语并提供解释',
      advanced: '使用专业术语,提供深入解释'
    };
    return levels[difficulty];
  }
  
  // 其他辅助方法...
}

进阶技巧篇:打造高性能定制代理

要点概览

  • 处理器组合可实现复杂业务逻辑,同时保持代码模块化
  • 钩子优先级机制可精确控制执行顺序
  • 缓存策略能显著提升代理响应速度和降低API成本

如何组合多个处理器实现复杂逻辑?

通过处理器链可以组合多个独立功能,实现复杂业务需求:

// 处理器1: 内容过滤
class ContentFilterProcessor extends BaseLlmRequestProcessor {
  async *runAsync(invocationContext: InvocationContext, llmRequest: LlmRequest) {
    // 过滤不当内容...
    return yield* this.nextProcessor.runAsync(invocationContext, llmRequest);
  }
}

// 处理器2: 请求优化
class RequestOptimizerProcessor extends BaseLlmRequestProcessor {
  async *runAsync(invocationContext: InvocationContext, llmRequest: LlmRequest) {
    // 优化请求大小和格式...
    return yield* this.nextProcessor.runAsync(invocationContext, llmRequest);
  }
}

// 处理器3: 响应格式化
class ResponseFormatterProcessor extends BaseLlmResponseProcessor {
  async *runAsync(invocationContext: InvocationContext, llmResponse: LlmResponse) {
    // 格式化响应为统一格式...
    return yield* this.nextProcessor.runAsync(invocationContext, llmResponse);
  }
}

// 组合使用处理器
const agent = new LlmAgent({
  requestProcessors: [
    BASIC_LLM_REQUEST_PROCESSOR,
    new ContentFilterProcessor(),
    new RequestOptimizerProcessor(),
    CONTENT_REQUEST_PROCESSOR
  ],
  responseProcessors: [
    new ResponseFormatterProcessor(),
    BASIC_LLM_RESPONSE_PROCESSOR
  ]
});

如何实现钩子优先级控制?

当多个钩子需要按特定顺序执行时,可以使用优先级机制:

// 带优先级的钩子实现
interface PrioritizedHook {
  priority: number; // 数值越大,执行优先级越高
  hook: Function;
}

// 钩子管理器
class HookManager {
  private hooks: Map<string, PrioritizedHook[]> = new Map();
  
  // 注册带优先级的钩子
  registerHook(type: string, hook: Function, priority: number = 0) {
    if (!this.hooks.has(type)) {
      this.hooks.set(type, []);
    }
    this.hooks.get(type)!.push({ priority, hook });
    
    // 按优先级排序
    this.hooks.get(type)!.sort((a, b) => b.priority - a.priority);
  }
  
  // 执行钩子
  async executeHooks(type: string, args: any): Promise<any> {
    const hooks = this.hooks.get(type) || [];
    let result;
    
    for (const { hook } of hooks) {
      result = await hook(args);
      // 如果钩子返回结果,短路执行
      if (result !== undefined) break;
    }
    
    return result;
  }
}

// 使用示例
const hookManager = new HookManager();

// 高优先级:安全检查
hookManager.registerHook('beforeModel', securityCheckHook, 100);

// 中优先级:日志记录
hookManager.registerHook('beforeModel', loggingHook, 50);

// 低优先级:性能监控
hookManager.registerHook('beforeModel', performanceHook, 10);

性能优化:请求缓存策略实现

实现请求缓存可以显著减少重复LLM调用,降低延迟和成本:

// 请求缓存处理器
class CachingRequestProcessor extends BaseLlmRequestProcessor {
  private cache: Map<string, LlmResponse> = new Map();
  private ttl: number = 3600000; // 缓存有效期1小时
  
  constructor(private cacheService: CacheService) {
    super();
  }
  
  async *runAsync(
    invocationContext: InvocationContext,
    llmRequest: LlmRequest
  ): AsyncGenerator<Event, void, LlmResponse> {
    // 1. 生成请求唯一标识
    const cacheKey = this.generateCacheKey(llmRequest);
    
    // 2. 尝试从缓存获取
    const cachedResponse = await this.cacheService.get(cacheKey);
    
    if (cachedResponse) {
      // 3. 返回缓存结果
      yield createEvent({
        type: 'cache_hit',
        content: { cacheKey, ttl: this.ttl }
      });
      return cachedResponse;
    }
    
    // 4. 缓存未命中,继续处理流程
    yield createEvent({ type: 'cache_miss', content: { cacheKey } });
    const response = yield* this.nextProcessor.runAsync(invocationContext, llmRequest);
    
    // 5. 缓存新结果
    await this.cacheService.set(cacheKey, response, this.ttl);
    
    return response;
  }
  
  // 生成缓存键
  private generateCacheKey(request: LlmRequest): string {
    // 基于请求内容生成唯一哈希
    const requestString = JSON.stringify(request);
    return createHash('md5').update(requestString).digest('hex');
  }
}

避坑指南篇:常见问题与解决方案

要点概览

  • 处理器顺序错误会导致意外行为,需遵循特定注册顺序
  • 钩子返回值处理不当可能导致流程异常终止
  • 内存泄漏常源于未清理的事件监听器和上下文引用

问题1:处理器执行顺序导致的功能异常

症状:自定义处理器未按预期修改请求内容 排查流程

  1. 检查处理器注册顺序,确保基础处理器先于自定义处理器
  2. 使用事件日志验证处理器执行顺序
  3. 确认处理器是否正确调用nextProcessor传递控制权

解决方案

// 正确的处理器顺序
const agent = new LlmAgent({
  requestProcessors: [
    // 1. 基础配置处理器(必须首先执行)
    BASIC_LLM_REQUEST_PROCESSOR,
    // 2. 身份和指令处理器
    IDENTITY_LLM_REQUEST_PROCESSOR,
    INSTRUCTIONS_LLM_REQUEST_PROCESSOR,
    // 3. 自定义处理器
    new CustomRequestProcessor(),
    // 4. 内容格式化处理器(最后执行)
    CONTENT_REQUEST_PROCESSOR
  ]
});

问题2:钩子短路执行导致意外结果

症状:代理在钩子执行后提前终止,未调用LLM 排查流程

  1. 检查钩子是否返回了非undefined值
  2. 验证isFinal标志是否被错误设置为true
  3. 检查钩子异常处理是否正确

解决方案

// 错误示例:意外短路
const badHook = async ({ request }) => {
  console.log('处理请求');
  // 错误:无意图地返回了对象,导致LLM调用被跳过
  return { debug: '处理完成' }; 
};

// 正确示例:不短路执行
const goodHook = async ({ request }) => {
  console.log('处理请求');
  // 正确:不返回值或返回undefined
  return undefined;
};

问题3:内存泄漏导致代理性能下降

症状:长时间运行后代理内存占用持续增长 排查流程

  1. 使用内存分析工具检测泄漏源
  2. 检查处理器/钩子中是否有全局缓存未设置过期策略
  3. 验证事件监听器是否正确移除

解决方案

// 错误示例:无限制缓存
class LeakyProcessor extends BaseLlmRequestProcessor {
  private cache = new Map(); // 永远不会清理
  
  async *runAsync(context, request) {
    const key = generateKey(request);
    if (!this.cache.has(key)) {
      this.cache.set(key, await computeValue());
    }
    // ...
  }
}

// 正确示例:带过期策略的缓存
class SafeProcessor extends BaseLlmRequestProcessor {
  private cache = new Map();
  private cacheExpiry = new Map();
  private ttl = 3600000; // 1小时过期
  
  async *runAsync(context, request) {
    const key = generateKey(request);
    const now = Date.now();
    
    // 清理过期缓存
    if (this.cacheExpiry.has(key) && now > this.cacheExpiry.get(key)) {
      this.cache.delete(key);
      this.cacheExpiry.delete(key);
    }
    
    if (!this.cache.has(key)) {
      this.cache.set(key, await computeValue());
      this.cacheExpiry.set(key, now + this.ttl);
    }
    // ...
  }
}

问题4:并发请求导致的上下文污染

症状:多用户同时使用时出现数据交叉污染 排查流程

  1. 检查是否在处理器/钩子中使用了共享状态
  2. 验证上下文对象是否正确隔离
  3. 检查会话管理逻辑是否存在漏洞

解决方案

// 错误示例:使用共享状态
class UnsafeProcessor extends BaseLlmRequestProcessor {
  private userData; // 所有请求共享的状态
  
  async *runAsync(context, request) {
    this.userData = context.session.get('userData'); // 会被后续请求覆盖
    // ...
  }
}

// 正确示例:使用局部状态
class SafeProcessor extends BaseLlmRequestProcessor {
  async *runAsync(context, request) {
    const userData = context.session.get('userData'); // 每个请求的局部变量
    // ...
  }
}

问题5:错误处理不当导致代理崩溃

症状:遇到异常时代理完全停止工作 排查流程

  1. 检查处理器/钩子中是否有未捕获的异常
  2. 验证错误处理逻辑是否覆盖所有可能失败点
  3. 检查异步操作是否正确处理拒绝状态

解决方案

// 错误示例:无错误处理
class RiskyProcessor extends BaseLlmRequestProcessor {
  async *runAsync(context, request) {
    // 没有try/catch,异常会传播并导致代理崩溃
    const data = await riskyOperation();
    // ...
  }
}

// 正确示例:完善的错误处理
class SafeProcessor extends BaseLlmRequestProcessor {
  async *runAsync(context, request) {
    try {
      const data = await riskyOperation();
      // ...
    } catch (error) {
      // 1. 记录错误
      yield createEvent({
        type: 'processor_error',
        level: 'error',
        content: { message: error.message, stack: error.stack }
      });
      
      // 2. 提供回退方案
      const fallbackData = getFallbackData();
      
      // 3. 继续处理流程或返回错误响应
      if (fallbackData) {
        // 使用回退数据继续
        request.contents.push({ role: 'system', parts: [{ text: '使用回退数据处理' }] });
        return yield* this.nextProcessor.runAsync(context, request);
      } else {
        // 返回错误响应
        return createErrorResponse('处理失败,请稍后重试');
      }
    }
  }
}

附录:项目结构与扩展资源

核心代码结构

ADK.js的LlmAgent相关核心代码位于以下目录:

扩展资源

  • 官方示例dev/samples/目录包含多种代理实现示例
  • 测试用例core/test/agents/提供处理器和钩子的测试示例
  • API文档:项目根目录下的docs/文件夹包含完整API文档

项目贡献

要为ADK.js贡献自定义处理器或钩子,请遵循以下步骤:

  1. 克隆项目仓库:git clone https://gitcode.com/GitHub_Trending/ad/adk-js
  2. core/src/agents/processors/目录下创建新处理器
  3. 添加相应的测试用例到core/test/agents/目录
  4. 提交PR并描述你的自定义组件功能和使用场景
登录后查看全文
热门项目推荐
相关项目推荐