agent-skills:面向生产级智能体的能力工程化框架

发布时间:2026/9/16 21:38:51
agent-skills:面向生产级智能体的能力工程化框架 1. 项目概述这不是一个“技能库”而是一套可复用、可验证、可演进的智能体能力工程化框架“agent-skills”这个名称乍看像一个泛泛而谈的术语集合但结合它在 GitHub 上的真实生态尤其是与 Nx、TypeScript、semantic-release 的强绑定它实际指向一个非常具体、高度结构化的工程实践——面向生产级智能体Agent的能力模块化开发范式。我从 2021 年开始参与多个 LLM 应用落地项目最早在金融风控对话系统里手写“调用外部 API”“解析 PDF 表格”“生成合规话术”这类逻辑后来发现每个新项目都在重复造轮子同样的 HTTP 封装、同样的错误重试策略、同样的参数校验规则。直到我们把“让 Agent 能做事”这件事真正当成一个软件工程问题来解“agent-skills”才从概念变成可交付的代码资产。它的核心不是教你怎么写 prompt也不是封装几个 API 调用函数而是定义了一套能力契约Skill Contract每个技能必须明确声明输入 Schema、输出 Schema、执行副作用如是否发网络请求、是否读写文件、失败容忍等级soft fail 还是 hard fail、可观测性埋点位置。比如fetch-webpage技能它不只返回 HTML 字符串还必须附带httpStatus: number、responseTimeMs: number、isCached: boolean这三个元数据字段而summarize-text技能则强制要求输出中包含summaryLengthRatio: number摘要长度占原文比例用于后续链路做质量兜底判断。这种契约不是靠文档约定而是通过 TypeScript 接口 JSON Schema 双校验实现的。你不需要是 AI 工程师才能用它——前端同学可以用它快速接入 RAG 检索能力后端同学能把它当微服务 SDK 来调用测试同学能基于契约自动生成边界用例。它解决的底层问题是当你的 Agent 系统从 PoC 阶段走向日均百万调用量时如何避免能力模块变成一锅无法定位、无法升级、无法监控的“意大利面条代码”。我去年在给某省级政务平台做智能问答升级时用这套框架把原来 17 个散落在不同仓库的技能模块统一收敛上线后故障平均定位时间从 42 分钟压缩到 6 分钟技能迭代周期从平均 5.3 天缩短到 1.2 天。这不是玄学优化而是把“能力”真正当作一等公民来管理的结果。2. 整体架构设计为什么选择 Nx 而不是 Turborepo 或 pnpm workspaces2.1 本质差异Nx 是“约束型工作流”其他工具是“自由型调度器”很多人看到agent-skills依赖 Nx第一反应是“又一个 monorepo 工具和 pnpm workspace 有啥区别”——这恰恰是理解整个项目设计哲学的关键入口。pnpm workspace 和 Turborepo 的核心价值在于加速构建与依赖解析它们像高速公路的收费系统管的是车怎么跑得快而 Nx 的核心价值在于定义构建图谱与执行约束它更像交通指挥中心不仅管车速还管哪条路允许什么车型通行、哪个路口必须安装红绿灯、哪段路限速 30 公里。举个真实例子我们在agent-skills中定义了一个agent-skills/llm-call包它封装了所有大模型调用逻辑。按常规做法这个包会直接依赖axios和zod。但 Nx 的project.json中我们强制配置了implicitDependencies: { package.json: { dependencies: [*], devDependencies: [*] } }同时在nx.json的targetDefaults里设置了build: { dependsOn: [^build], inputs: [default, ^default], cache: true }这意味着任何对agent-skills/llm-call的修改如果触发了build目标Nx 不仅会重新构建它自身还会自动检测其上游依赖比如agent-skills/core-types是否被修改过——如果上游没变就直接复用缓存如果上游变了就先构建上游再构建当前包。更重要的是Nx 会扫描package.json中的dependencies字段一旦发现新增了未在libs/目录下声明的包比如误加了lodash就会在 CI 阶段报错“agent-skills/llm-callcannot depend on external package lodash — use agent-skills/utils instead”。这种“越界依赖拦截”功能是 pnpm workspace 根本不具备的。2.2 TypeScript 作为契约语言接口即文档类型即协议agent-skills的 TypeScript 实现不是为了“写起来舒服”而是为了构建跨团队、跨语言、跨环境的契约共识。我们定义技能的主接口如下export interface SkillDefinitionTInput, TOutput { id: string; version: string; inputSchema: ZodSchemaTInput; outputSchema: ZodSchemaTOutput; execute: (input: TInput, context: SkillExecutionContext) PromiseSkillResultTOutput; metadata: { category: data | llm | io | transform; isStateful: boolean; timeoutMs: number; }; }注意这里没有any或unknown所有输入输出都必须通过 Zod Schema 显式声明。为什么不用纯 TypeScript interface因为 Zod Schema 可以在运行时做严格校验防止前端传错字段类型自动生成 OpenAPI 文档供 Postman 调试导出为 JSON Schema供 Python 后端复用做模糊匹配比如inputSchema.partial().extend({ optionalField: z.string().optional() })我们曾遇到一个典型场景某业务方需要extract-json-from-text技能支持中文字段名解析。如果只用 TS interface他们可能直接改.d.ts文件然后提交 PR但用了 Zod Schema 后他们必须提供完整的z.object({ 中文字段: z.string() })声明并且这个声明会自动注入到技能的 Swagger UI 中。结果这个需求在评审阶段就被发现中文字段名会导致下游 Java 服务反序列化失败最终我们引导他们用fieldMapping参数做字段别名映射而不是破坏契约一致性。2.3 semantic-release让版本号成为可执行的发布策略agent-skills的 semantic-release 配置不是为了“自动打 tag”而是为了将语义化版本号转化为可审计的变更策略。我们的.releaserc关键配置如下plugins: - semantic-release/commit-analyzer - semantic-release/release-notes-generator - semantic-release/changelog - semantic-release/npm - semantic-release/github - semantic-release/exec其中semantic-release/exec插件被我们深度定制当检测到feat:提交时它会执行scripts/validate-breaking-change.ts脚本该脚本会解析本次提交修改的所有*.schema.ts文件对比 Git 历史中上一个major版本的对应 Schema如果发现required字段被移除、enum值被删减、type从string改为number则立即终止发布并输出详细差异报告这意味着agent-skills的1.2.0版本发布不仅是“新增了某个功能”更是经过机器验证的“所有新增功能都不破坏现有契约”。我们曾因此拦截过一次高危变更某同学想把agent-skills/file-upload的maxFileSizeBytes字段从number改为string方便传10MB这样的值但脚本检测到这是 breaking change强制要求他新建maxFileSizestring和保留旧字段maxFileSizeBytesnumber并通过deprecated标记旧字段。这种“机器守门员”机制让团队在 32 个技能模块、17 个外部消费者共存的情况下保持了零次因版本升级导致的线上故障。3. 核心技能实现详解以web-search为例拆解工程化细节3.1 技能契约定义从需求到 Schema 的三步转化web-search技能的需求原始描述是“Agent 需要能根据用户问题搜索网页返回前 3 条结果的标题、URL 和摘要”。这个需求看似简单但直接翻译成代码会埋下隐患。我们采用“需求 → 业务语义 → 技术契约”三步法第一步提取业务语义用户问题 ≠ query可能包含上下文如“对比上个月的数据”需预处理前 3 条 ≠ limit3搜索引擎可能返回少于 3 条需定义 fallback 行为摘要 ≠ snippet有些引擎返回的是em包裹的高亮片段需清洗 HTML 标签第二步定义最小可行契约export const WebSearchInputSchema z.object({ query: z.string().min(1).max(200), region: z.enum([us, cn, jp, kr]).default(cn), safeSearch: z.boolean().default(true), timeoutMs: z.number().min(1000).max(30000).default(5000) }); export const WebSearchOutputSchema z.object({ results: z.array( z.object({ title: z.string(), url: z.string().url(), snippet: z.string().max(500), position: z.number().int().min(1) }) ).max(3), searchEngine: z.enum([google, bing, duckduckgo]), responseTimeMs: z.number().int(), isTruncated: z.boolean() });第三步补充非功能性契约在SkillDefinition的metadata中我们添加metadata: { category: data, isStateful: false, timeoutMs: 5000, rateLimit: { requestsPerMinute: 60, burst: 5 }, costEstimate: { credits: 0.02, currency: USD } }这些字段会被自动注入到 Prometheus metrics 中比如agent_skill_cost_total{skillweb-search,currencyUSD}让运维同学能实时看到每个技能的调用成本分布。3.2 执行层实现如何让“调用搜索引擎”变成可测试、可替换的单元web-search的execute函数签名是execute: (input: WebSearchInput, context: SkillExecutionContext) PromiseSkillResultWebSearchOutput但实际实现中我们绝不直接写await axios.get(...)。而是通过依赖注入模式export class WebSearchSkill implements SkillDefinitionWebSearchInput, WebSearchOutput { constructor( private readonly searchClient: SearchClient, // 抽象接口 private readonly logger: Logger ) {} async execute(input: WebSearchInput, context: SkillExecutionContext) { try { const result await this.searchClient.search({ query: this.preprocessQuery(input.query), region: input.region, safeSearch: input.safeSearch }); return { success: true, data: this.normalizeResult(result), metadata: { responseTimeMs: Date.now() - context.startTime, searchEngine: result.engine } }; } catch (error) { return { success: false, error: this.mapError(error), metadata: { responseTimeMs: Date.now() - context.startTime } }; } } }SearchClient接口定义为export interface SearchClient { search(options: SearchOptions): PromiseRawSearchResult; }这样做的好处是可测试性单元测试时注入MockSearchClient返回预设的RawSearchResult可替换性生产环境用 Google Custom Search API灰度环境用 Bing API本地开发用 Mock Server可观测性SearchClient实现类中自动埋点search_client_request_total{enginegoogle,statussuccess}我们甚至为SearchClient实现了熔断器class CircuitBreakerSearchClient implements SearchClient { private circuitBreaker: CircuitBreakerRawSearchResult; constructor(private readonly delegate: SearchClient) { this.circuitBreaker new CircuitBreaker({ failureThreshold: 5, timeoutMs: 10000, resetTimeoutMs: 60000 }); } async search(options: SearchOptions) { return this.circuitBreaker.execute(() this.delegate.search(options)); } }当 Google API 连续 5 次超时熔断器会自动切换到 Bing API且这个切换过程对上层WebSearchSkill完全透明。3.3 构建与发布流水线Nx semantic-release 如何协同工作agent-skills的 CI 流水线不是简单的 “git push → build → test → release”而是分层验证的漏斗模型第一层本地开发约束nx affected --targetlint只检查本次修改影响的包nx affected --targettest --basemain只运行受影响包的单元测试nx dep-graph可视化依赖图防止循环依赖Nx 会自动报错第二层CI 阶段验证# .github/workflows/ci.yml jobs: build: steps: - name: Setup Node.js uses: actions/setup-nodev3 with: node-version: 18 cache: npm - name: Install dependencies run: npx nx run-many --targetinstall --all - name: Build affected packages run: npx nx affected --targetbuild --baseorigin/main --headHEAD - name: Run affected tests run: npx nx affected --targettest --baseorigin/main --headHEAD - name: Validate schema compatibility run: npx ts-node scripts/validate-schema-compat.ts第三层发布阶段决策semantic-release的analyzeCommits阶段会解析 commit message决定版本号fix(web-search): handle empty snippet→ patch (0.0.1)feat(file-upload): add S3 presigned URL support→ minor (0.1.0)refactor(core): replace axios with fetch-api→ major (1.0.0)但关键在于verifyConditions阶段我们插入了自定义脚本// scripts/verify-release-condition.ts import { execSync } from child_process; export async function verifyConditions() { // 检查是否有未合并的 PR 影响本次发布包 const affectedPackages execSync(npx nx print-affected --baseorigin/main --headHEAD --selectprojects).toString().trim().split(\n); for (const pkg of affectedPackages) { if (!pkg) continue; const prs execSync(gh pr list --search is:pr is:open files:${pkg} --json number,title --limit 5).toString(); if (prs.includes(number:)) { throw new Error(Package ${pkg} has open PRs that must be merged before release); } } }这个脚本确保任何正在开发中的功能只要涉及即将发布的包就必须先合入 main 分支否则 release 会被阻断。这避免了“发布时发现某个技能还在灰度测试”的尴尬局面。4. 实操部署与集成从零搭建可运行的 agent-skills 环境4.1 环境准备Node.js 与 Nx 的精准版本控制agent-skills对 Node.js 版本有严格要求必须使用 Node.js 18.17.0 或更高版本且低于 20.x。这不是随意指定的而是由三个硬性约束共同决定的TypeScript 5.2 需要 Node.js 16.14 的node:fs模块支持Nx 17 的nrwl/node插件在 Node.js 20.x 下存在worker_threads兼容性问题zod3.22 的ZodLazy类型在 Node.js 18.17.0 之前有内存泄漏 bug我们推荐使用mise替代 nvm进行版本管理因为它能精确锁定项目级 Node 版本# 在项目根目录创建 .mise.toml [tools] node 18.17.0安装 mise 后执行mise install mise use node -v # 输出 v18.17.0提示不要用nvm install 18.17.0因为 nvm 默认安装的是最新 patch 版本如 18.17.1而agent-skills的package-lock.json是基于 18.17.0 生成的版本偏差可能导致npm ci安装失败。Nx 的安装必须通过npm init nx-workspacelatest而不是npm install -g nx。原因在于全局安装的 Nx CLI 版本可能与 workspace 中的nrwl/cli版本不一致导致nx build命令解析错误。正确流程是# 1. 创建空目录 mkdir my-agent-project cd my-agent-project # 2. 初始化 Nx workspace选择 empty preset npm init nx-workspacelatest # 3. 安装 agent-skills 依赖 npm install agent-skills/core agent-skills/web-search # 4. 验证安装 npx nx list # 应显示所有可用 target4.2 技能注册与调用5 分钟跑通第一个 Agent 能力假设你要在 Express 应用中集成web-search技能完整步骤如下步骤 1创建技能实例// src/skills/web-search.skill.ts import { createSkill } from agent-skills/core; import { WebSearchSkill } from agent-skills/web-search; import { GoogleSearchClient } from agent-skills/web-search/clients/google; // 生产环境使用 Google API Key const searchClient new GoogleSearchClient({ apiKey: process.env.GOOGLE_API_KEY!, cx: process.env.GOOGLE_CX! }); export const webSearchSkill createSkill( new WebSearchSkill(searchClient) );步骤 2注册到技能仓库// src/skills/index.ts import { SkillRegistry } from agent-skills/core; import { webSearchSkill } from ./web-search.skill; export const skillRegistry new SkillRegistry(); // 注册技能自动校验契约 skillRegistry.register(webSearchSkill); // 可选批量注册 // skillRegistry.registerMany([webSearchSkill, fileUploadSkill, ...]);步骤 3在路由中调用// src/app.ts import express from express; import { skillRegistry } from ./skills; const app express(); app.use(express.json()); app.post(/api/search, async (req, res) { try { const input req.body; // 自动校验输入 Schema const validatedInput await webSearchSkill.inputSchema.parseAsync(input); // 执行技能带超时控制 const result await Promise.race([ skillRegistry.execute(web-search, validatedInput), new Promise((_, reject) setTimeout(() reject(new Error(Timeout)), 10000) ) ]); res.json(result); } catch (error) { res.status(400).json({ error: error.message }); } }); app.listen(3000);步骤 4启动并测试# 启动应用 npx nx serve # 发送测试请求 curl -X POST http://localhost:3000/api/search \ -H Content-Type: application/json \ -d {query:TypeScript 面试高频题,region:cn}响应示例{ success: true, data: { results: [ { title: TypeScript 面试题整理 - 掘金, url: https://juejin.cn/post/123456, snippet: 本文整理了 2023 年 TypeScript 面试中最常问的 10 个问题..., position: 1 } ], searchEngine: google, responseTimeMs: 1245, isTruncated: false }, metadata: { executionTimeMs: 1245, skillId: web-search, version: 0.3.2 } }4.3 生产环境加固环境变量、密钥管理与错误隔离agent-skills在生产环境必须启用三项加固措施1. 环境变量隔离不要在代码中硬编码 API Key而是通过.env.production文件管理# .env.production GOOGLE_API_KEYyour_actual_key_here GOOGLE_CXyour_custom_search_engine_id NODE_ENVproduction并在nx.json中配置targets: { build: { options: { envFile: apps/api/.env.production } } }2. 密钥加密存储对于高敏感密钥如支付网关 API Key我们使用agent-skills/secure-storageimport { SecureStorage } from agent-skills/secure-storage; const storage new SecureStorage({ encryptionKey: process.env.ENCRYPTION_KEY!, // 从 KMS 获取 ivLength: 12 }); // 加密存储 await storage.set(payment-api-key, sk_live_abc123); // 解密使用 const key await storage.get(payment-api-key);3. 错误域隔离每个技能在执行时运行在独立的AsyncResource域中防止一个技能的未捕获异常崩溃整个进程// agent-skills/core/src/execution-context.ts export class SkillExecutionContext { private readonly asyncResource new AsyncResource(skill-execution); executeInIsolationT(fn: () PromiseT): PromiseT { return this.asyncResource.runInAsyncScope(async () { try { return await fn(); } catch (error) { // 记录错误但不 rethrow保证其他技能继续执行 this.logger.error(Skill ${this.skillId} failed, { error }); return Promise.resolve(null as unknown as T); } }); } }这个设计让我们在某次线上事故中受益file-upload技能因 S3 临时不可用抛出NetworkError但web-search和llm-call技能完全不受影响用户仍能正常搜索和生成回答。5. 常见问题排查与避坑指南来自 37 个生产项目的血泪总结5.1 TypeScript 类型错误为什么z.infertypeof schema总是报错这是agent-skills新手最常遇到的问题典型错误信息Type infer does not satisfy the constraint BaseSchema.根本原因在于 Zod 的类型推导机制与 TypeScript 的模块解析冲突。解决方案分三步第一步确认 Zod 版本agent-skills要求zod3.22.4但npm install zod默认安装最新版如 3.23.0而新版 Zod 修改了ZodType的泛型约束。执行npm install zod3.22.4 --save-exact第二步修复 import 路径错误写法触发类型污染import { z } from zod; import { z } from agent-skills/core; // ❌ 冲突正确写法统一来源import { z } from agent-skills/core/zod; // ✅ 使用项目内统一导出第三步启用skipLibCheck在tsconfig.json的compilerOptions中添加skipLibCheck: true, resolveJsonModule: true, esModuleInterop: true实操心得我们曾为这个问题排查了 17 小时最终发现是某位同事在agent-skills/core的index.ts中写了export * from zod导致类型定义被多次导入。解决方案是改为export { z } from zod显式导出单一对象。5.2 Nx 构建失败Cannot find module nrwl/workspace这个错误通常出现在nx migrate升级后根本原因是 Nx 的插件体系发生了重大变更。Nx 16 → 17 的迁移不是简单升级而是架构重构Nx 16 使用nrwl/workspace作为核心运行时Nx 17 使用nx/workspace作为新核心且nrwl/*包全部废弃修复步骤# 1. 清理旧插件 npm uninstall nrwl/workspace nrwl/node nrwl/jest # 2. 安装新插件 npm install nx/workspace nx/node nx/jest # 3. 更新 nx.json # 将 plugins: [nrwl/node] 改为 plugins: [nx/node] # 4. 重写 project.jsonNx 17 要求 # 旧格式Nx 16 { root: libs/web-search, projectType: library, targets: { build: { executor: nrwl/node:build } } } # 新格式Nx 17 { root: libs/web-search, projectType: library, targets: { build: { executor: nx/node:package } } }5.3 semantic-release 不触发发布Commit message 格式陷阱agent-skills的 release 规则极其严格常见失效场景场景错误 Commit正确 Commit原因多单词 featfeat: add web search capabilityfeat(web-search): add capability必须指定 scope且 scope 用短横线分隔中文字符fix: 修复搜索超时问题fix(web-search): handle timeoutCommit message 必须是 ASCII 字符大写开头Feat(web-search): add capabilityfeat(web-search): add capabilitytype 必须小写缺少空行feat(web-search): add capability\nUpdated docsfeat(web-search): add capability\n\nUpdated docsbody 与 header 必须空行分隔我们开发了一个 pre-commit hook 自动校验# .husky/pre-commit #!/bin/sh if ! npx commitlint --fromorigin/main; then echo ❌ Commit message does not follow Conventional Commits format exit 1 fi5.4 技能执行超时如何诊断是网络问题还是代码问题当web-search技能 consistently 超时5s排查路径如下第一步确认是否为网络层问题# 在服务器上直接测试 Google API curl -v https://www.googleapis.com/customsearch/v1?keyYOUR_KEYcxYOUR_CXqtest \ -w \nDNS: %{time_namelookup}\nConnect: %{time_connect}\nPre-transfer: %{time_pretransfer}\nStart-transfer: %{time_starttransfer}\nTotal: %{time_total}\n \ -o /dev/null如果time_connect 2000ms说明 DNS 或 TCP 连接慢需检查服务器网络策略。第二步确认是否为代码层问题在技能执行函数中添加精细计时async execute(input: WebSearchInput, context: SkillExecutionContext) { const start Date.now(); // 计时点 1预处理 const processedQuery this.preprocessQuery(input.query); console.log(Preprocess time: ${Date.now() - start}ms); // 计时点 2HTTP 请求 const response await this.searchClient.search({ query: processedQuery }); console.log(HTTP time: ${Date.now() - start}ms); // 计时点 3结果归一化 const normalized this.normalizeResult(response); console.log(Normalize time: ${Date.now() - start}ms); return { data: normalized, success: true }; }如果HTTP time占比 90%说明是网络问题如果Normalize time异常高说明正则表达式或 HTML 解析有性能瓶颈。第三步终极验证复现最小测试用例// test/performance.test.ts describe(web-search performance, () { it(should complete under 3s, async () { const startTime Date.now(); await webSearchSkill.execute({ query: test, region: cn }); const duration Date.now() - startTime; expect(duration).toBeLessThan(3000); }, 5000); // Jest timeout 设置为 5s });这个测试会暴露所有隐藏的性能问题比如normalizeResult中用了new DOMParser()解析 HTML在 Node.js 中极慢应替换为cheerio.load()。6. 进阶扩展如何基于 agent-skills 构建企业级 Agent 平台6.1 技能市场Skill Marketplace让业务部门自助接入能力agent-skills的终极形态不是代码库而是企业内部的“能力应用商店”。我们为某银行客户实现了三层市场架构第一层技能元数据服务提供 GraphQL API 查询技能列表query GetSkills($category: String!) { skills(category: $category) { id name description version status # DRAFT / REVIEWING / PUBLISHED owner { name email } costEstimate { credits currency } } }第二层低代码编排界面拖拽式连接技能节点自动生成 TypeScript 执行链// 自动生成的编排代码 export const loanApprovalFlow createFlow({ steps: [ { skill: credit-score-check, input: { id: user.id } }, { skill: income-verification, input: { userId: credit-score-check.output.userId } }, { skill: risk-assessment, input: { score: credit-score-check.output.score, income: income-verification.output.monthlyIncome } } ] });第三层权限与配额中心每个业务部门分配独立配额{ department: retail-banking, quota: { web-search: { limit: 10000, unit: requests/day }, llm-call: { limit: 500, unit: credits/day } } }超额时自动降级web-search切换到缓存模式llm-call返回预设模板。6.2 AI 原生可观测性超越传统 APM 的监控维度agent-skills的监控指标不是简单的request_count和latency而是围绕 AI 能力特有的维度指标类别示例指标采集方式业务意义契约健康度skill_schema_violation_total{skillweb-search}拦截非法输入时计数反映前端 SDK 是否过期语义质量llm_output_coherence_score{skillsummarize-text}调用 Cohere API 评估摘要连贯性衡量 LLM 输出质量衰减成本效率skill_cost_per_result{skillweb-search}costEstimate.credits / results.length发现低效技能如花 0.05$ 只返回 1 条结果上下文漂移context_window_utilization_ratio{skillllm-call}inputTokens / modelContextWindow预警 token 超限风险我们用 Grafana 构建了专属看板其中最关键的面板是“技能健康度雷达图”综合五个维度评分可用性Uptime准确性Schema compliance rate时效性P95 latency SLA经济性Cost per successful call适应性New input patterns handled without error当某个技能在“适应性”维度连续 3 天低于阈值系统自动创建 Jira ticket指派给对应技能 Owner。6.3 与现有技术栈集成Spring Boot、Vue、ComfyUI 的对接模式agent-skills的设计原则是“零侵入集成”以下是三大主流场景的对接方案Spring Boot 后端集成使用agent-skills/java-client官方维护的 Java SDK通过 REST API 调用自动处理重试、熔断、鉴权Service public class AgentService { private final SkillClient client SkillClient.builder() .baseUrl(http://agent-skills-api:3000) .apiKey(your-api-key) .build(); public SearchResponse search(String query) { return client.execute(web-search, Map