LLM Skills工程化实践:从函数到可调度微服务
1. 这不是“技能列表”而是一套可执行、可验证、可进化的工程化能力体系你搜“skills”时看到的那些词——Google Cloud、GKE、Gemini、Agent Platform、superpower skills、gemini code assist、claude agent skills、codex写论文、自动挖洞skills、分镜skills……它们根本不是零散的功能点更不是App Store里点几下就能装上的“插件”。它们是同一类东西在特定技术栈与运行环境中被封装为标准化接口、具备明确输入输出契约、能被调度器发现并编排调用的可复用能力单元。我做Agent系统开发三年从最早手写Python函数暴露HTTP端点到后来用LangChain Tools规范定义再到现在基于Google Agent Platform或Claude Tool Calling协议构建踩过所有坑才明白所谓“skills”本质是面向LLM时代的微服务新形态——它不靠UI交互而靠语义理解触发不靠人工点击而靠推理链自动调度不靠本地安装包而靠注册中心动态发现。为什么现在满屏都是“skills”因为大模型本身没有IO能力、没有状态记忆、没有外部系统访问权限。它像一个绝顶聪明但被关在玻璃房里的博士能推演一切却连打开浏览器都做不到。而skills就是给它配上的手、眼、耳、脚和银行卡。前端开发skills不是教它写React而是让它能调用Vite CLI生成项目结构superpower skills不是魔法而是把Selenium自动化、Playwright截图、PDF解析、数据库查询这些能力统一包装成{name: web_screenshot, description: Take screenshot of given URL, parameters: {url: string}}这样的JSON Schemagemini登录失败提示“your account is not eligible”那不是账号问题而是你没在Google Cloud Console里为对应Project启用Agent Platform API也没配置Service Account密钥绑定到Gemini调用链路——这背后是IAM权限策略、API启用状态、配额限制三重校验缺一不可。真正懂skills的人一眼就能从报错信息反推出缺失的GCP配置项。这篇文章不讲概念只讲你明天上班就能用上的实操逻辑怎么设计一个skills、怎么部署它、怎么让它被Gemini或Claude真正识别并调用、怎么调试它在真实Agent流程中掉链子的环节。适合正在搭建内部Copilot、想接入Gemini Code Assist但卡在权限页、或者被“skills下载平台有哪些”这种搜索结果搞晕的新手和中级开发者。2. skills的本质解构从函数签名到Agent可调度单元的四层跃迁2.1 第一层原始函数——有功能但LLM看不见最基础的skills就是一段能干活的代码。比如一个查天气的Python函数def get_weather(city: str) - str: import requests resp requests.get(fhttps://api.openweathermap.org/data/2.5/weather?q{city}appidxxx) data resp.json() return f{city}当前温度{data[main][temp]-273.15:.1f}℃天气{data[weather][0][description]}它能跑但对LLM来说等于不存在。原因有三无元数据LLM不知道这个函数叫什么、能干什么、需要什么参数无契约没有明确的输入类型约束city: str在运行时才校验LLM可能传入空字符串或数字导致崩溃无上下文函数内部硬编码API Key无法适配多租户场景也无法被安全审计。我第一次把这类函数塞进Agent时Gemini连续三次生成get_weather(北京)调用但实际执行环境里Key已过期日志里只有一行ConnectionError根本没法定位是Key失效还是网络问题。这就是纯函数阶段的典型困境能力存在但不可见、不可控、不可追溯。2.2 第二层工具描述——让LLM“读得懂”的最小契约要让LLM理解并调用必须提供机器可读的描述。Google Agent Platform要求的Tool Schema长这样{ name: get_weather, description: Get current weather information for a specified city, parameters: { type: object, properties: { city: { type: string, description: The name of the city, e.g., Beijing, New York } }, required: [city] } }注意三个关键点description必须用自然语言且动词开头、对象明确、不含模糊修饰。写成“获取天气”LLM会困惑“获取指定城市的当前天气信息”才能精准匹配用户queryparameters里每个字段都要有description这是LLM生成参数值的唯一依据。曾有个同事写city: {type: string}漏掉descriptionGemini就生成{city: weather_api_key}——它把参数名当成了值required数组强制声明必填项避免LLM传空参导致下游崩溃。这一层解决了“可见性”但还没解决“可执行性”。Schema只是说明书函数还得自己部署、自己暴露HTTP端点、自己处理鉴权。这时候skills还只是纸面协议。2.3 第三层可调度服务——运行时的标准化载体Google Agent Platform和Claude Tool Calling都要求skills以符合OpenAPI 3.0规范的HTTP服务形式存在。不是随便起个Flask服务就行必须满足端点路径固定Google要求POST /v1/tools/{tool_name}:invokeClaude要求POST /tools/{tool_name}请求体结构严格Google传{input: {city: Beijing}}Claude传{city: Beijing}差一个嵌套层级就400响应体必须含output字段Google要求{output: 北京当前温度25.3℃...}返回纯字符串直接报错。我部署第一个skills时在GKE上用Cloud Run跑了一个FastAPI服务但忘了在pydantic.BaseModel里加output: str字段Gemini调用后返回{result: ...}平台日志里清清楚楚写着Missing required field output in tool response。查了两小时才发现是Pydantic模型定义漏了字段。这说明skills不是写完函数就完事它是客户端LLM和服务端你的代码之间的一份双向SLA。任何一方违约整个链路就断。2.4 第四层平台集成——被发现、被授权、被编排的完整生命周期真正的skills必须注册到平台的“能力注册中心”。在Google Cloud里这通过Agent Platform的Tools资源完成gcloud alpha generative-ai agents tools create \ --locationus-central1 \ --agent-idyour-agent-id \ --tool-nameget_weather \ --tool-spec-filetool_spec.json \ --service-endpointhttps://your-service.run.app这一步触发三件事平台向你的服务端点发送GET /health探针验证可用性自动注入Service Account Token到HTTP Header你的服务必须校验Authorization: Bearer xxx将tool spec存入平台元数据库Gemini推理时才能从注册中心拉取最新Schema。很多人卡在“gemini code assist not eligible”根源就在这里你的GCP Project没启用generativelanguage.googleapis.comAPI没给Gemini服务账号service-PROJECT_NUMBERgcp-sa-aiplatform.iam.gserviceaccount.com授予roles/generativelanguage.toolUser角色或者Agent Platform本身没在Project里初始化gcloud alpha generative-ai agents init。这不是账号问题是基础设施配置缺失。就像你想用Docker但宿主机连Docker daemon都没装。3. 实战部署从本地开发到GKE生产环境的全链路拆解3.1 本地开发环境用FastAPIPydantic快速验证契约别一上来就怼GKE。先在本地用FastAPI搭个最小可行服务重点验证LLM能正确生成参数、你的服务能正确解析并返回。核心文件结构weather_skill/ ├── main.py # FastAPI应用 ├── models.py # Pydantic模型定义 └── requirements.txtmodels.py定义输入输出契约from pydantic import BaseModel from typing import Optional class WeatherInput(BaseModel): city: str units: Optional[str] metric # 允许LLM省略默认摄氏度 class WeatherOutput(BaseModel): output: str # 必须叫outputGoogle强制要求main.py实现端点from fastapi import FastAPI, HTTPException, Depends from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials import requests from models import WeatherInput, WeatherOutput app FastAPI() security HTTPBearer() # 后续对接GCP Auth用 app.post(/v1/tools/get_weather:invoke, response_modelWeatherOutput) async def invoke_weather( input_data: WeatherInput, credentials: HTTPAuthorizationCredentials Depends(security) ): # 1. 校验Token本地先跳过上线必须开 # 2. 调用OpenWeather API try: resp requests.get( fhttps://api.openweathermap.org/data/2.5/weather, params{q: input_data.city, appid: YOUR_KEY, units: input_data.units}, timeout5 ) resp.raise_for_status() data resp.json() temp data[main][temp] - 273.15 if input_data.units metric else data[main][temp] desc data[weather][0][description] return WeatherOutput(outputf{input_data.city}当前温度{temp:.1f}℃天气{desc}) except requests.exceptions.Timeout: raise HTTPException(504, Weather API timeout) except Exception as e: raise HTTPException(500, fWeather API error: {str(e)})启动命令uvicorn main:app --reload --port 8000。用curl测试curl -X POST http://localhost:8000/v1/tools/get_weather:invoke \ -H Content-Type: application/json \ -d {input: {city: Shanghai}} # 返回{output:上海当前温度28.5℃天气clear sky}这步验证了契约正确性。如果返回422一定是Pydantic模型字段名或嵌套结构不对如果返回500检查API Key或网络。3.2 GCP环境准备三步打通Agent Platform血脉本地跑通后必须在GCP完成三件基础设施级配置否则skills永远注册不上去步骤1启用必需API# 必须启用缺一不可 gcloud services enable \ generativelanguage.googleapis.com \ aiplatform.googleapis.com \ run.googleapis.com \ cloudbuild.googleapis.com提示generativelanguage.googleapis.com是Gemini核心APIaiplatform.googleapis.com是Agent Platform底座run.googleapis.com是Cloud Run部署依赖。很多人只开第一个结果注册tool时提示Permission denied on resource project——其实是第二个API没启用。步骤2创建专用Service Account并授权# 创建SA gcloud iam service-accounts create weather-sa \ --display-nameWeather Skill SA # 授予必要角色 gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \ --memberserviceAccount:weather-saYOUR_PROJECT_ID.iam.gserviceaccount.com \ --roleroles/generativelanguage.toolUser gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \ --memberserviceAccount:weather-saYOUR_PROJECT_ID.iam.gserviceaccount.com \ --roleroles/run.invoker注意toolUser角色允许该SA调用Agent Platform工具run.invoker允许Gemini调用你的Cloud Run服务。少一个调用时就会403 Forbidden。步骤3初始化Agent Platform# 创建默认Agent会自动生成ID gcloud alpha generative-ai agents create \ --locationus-central1 \ --display-nameMy Copilot # 记下返回的agent_id后续注册tool要用这三步做完你的GCP环境才真正准备好接纳skills。跳过任何一步后面所有操作都是空中楼阁。3.3 Cloud Run部署用Docker镜像实现GKE级弹性伸缩GKE不是必须选项Cloud Run更轻量且自动扩缩。但必须用容器化部署因为Google要求服务必须支持HTTPS和健康检查需要注入Service Account Token多实例负载均衡需容器原生支持。Dockerfile内容FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [uvicorn, main:app, --host, 0.0.0.0:8080, --port, 8080]requirements.txtfastapi0.115.0 uvicorn[standard]0.32.0 httpx0.27.0 pydantic2.9.2构建并部署# 构建镜像用GCP Artifact Registry gcloud artifacts repositories create weather-repo --repository-formatdocker --locationus-central1 --descriptionWeather skill images # 推送镜像 docker build -t us-central1-docker.pkg.dev/YOUR_PROJECT_ID/weather-repo/weather-skill . docker push us-central1-docker.pkg.dev/YOUR_PROJECT_ID/weather-repo/weather-skill # 部署到Cloud Run gcloud run deploy weather-skill \ --image us-central1-docker.pkg.dev/YOUR_PROJECT_ID/weather-repo/weather-skill \ --platform managed \ --region us-central1 \ --allow-unauthenticated \ --service-account weather-saYOUR_PROJECT_ID.iam.gserviceaccount.com \ --set-env-varsOPENWEATHER_API_KEYYOUR_KEY \ --cpu 1 --memory 512Mi关键参数说明--service-account绑定之前创建的SA确保调用时自动注入Token--set-env-vars安全传递API Key避免硬编码--cpu 1 --memory 512Miskills服务通常轻量但LLM并发调用时需足够内存解析JSON512Mi是实测稳定值。部署成功后Cloud Run会分配一个https://weather-skill-xxxx.a.run.app域名。把这个URL填入tool spec的serviceEndpoint字段。3.4 Tool注册与测试用gcloud命令完成最后临门一脚写好tool spec JSON文件tool_spec.json{ name: get_weather, description: Get current weather information for a specified city, parameters: { type: object, properties: { city: { type: string, description: The name of the city, e.g., Beijing, New York }, units: { type: string, description: Temperature unit: metric (Celsius) or imperial (Fahrenheit), default: metric } }, required: [city] }, functionCallingSpec: { serviceEndpoint: https://weather-skill-xxxx.a.run.app } }注册命令gcloud alpha generative-ai agents tools create \ --locationus-central1 \ --agent-idYOUR_AGENT_ID \ --tool-nameget_weather \ --tool-spec-filetool_spec.json注册成功后用Gemini Playground测试在Prompt里输入“北京今天天气怎么样”开启“Function calling”开关观察LLM是否生成{name: get_weather, args: {city: 北京}}查看Cloud Run日志确认请求到达且返回{output: ...}如果LLM没触发调用检查Agent是否绑定了这个toolgcloud alpha generative-ai agents tools list --agent-id...tool spec里description是否足够清晰试试改成“查询指定城市的实时气温和天气状况”Cloud Run服务是否开启“Allow unauthenticated”测试期必须开生产环境应关闭并改用IAM验证。4. 真实故障排查手册从Gemini报错日志到GKE事件的逐层溯源4.1 常见报错速查表按错误码和现象分类现象错误码/日志片段根本原因解决方案Gemini不触发任何tool callPrompt中无明确动作指令LLM未识别出需要调用外部能力在Prompt末尾加约束“如果需要查询实时信息请务必调用可用tools”调用返回401 UnauthorizedCloud Run日志显示Authorization header missingService Account Token未注入或校验失败检查部署时--service-account参数在代码中添加credentials.credentials打印验证调用返回404 Not FoundCloud Run日志无请求记录tool spec中serviceEndpointURL错误或路径不匹配用curl手动测试https://your-service.a.run.app/v1/tools/get_weather:invoke调用返回422 Unprocessable EntityFastAPI返回detail: [{loc:[body,input],msg:field required,type:value_error.missing}]LLM生成的JSON结构与Pydantic模型不匹配检查tool_spec.json中parameters字段名是否与Pydantic模型完全一致大小写敏感调用返回504 Gateway TimeoutCloud Run日志显示timeout after 60sOpenWeather API响应慢或网络延迟高在FastAPI中设置timeout5增加重试逻辑考虑缓存热门城市结果4.2 深度排查技巧如何从LLM输出反推底层问题当Gemini返回{name: get_weather, args: {city: }}空字符串时不要急着改代码。先做三件事抓取原始LLM输出在Agent Platform控制台开启“Debug mode”查看Gemini生成的完整Function Call JSON比对tool spec确认tool_spec.json中city字段的description是否写成“城市名称如北京、纽约”而不是“请输入城市名”——后者会让LLM认为这是提示语而非参数说明模拟LLM推理用curl -X POST https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?keyYOUR_KEY发送相同prompt观察是否生成相同错误args。如果是问题在LLM理解层需优化prompt或tool description如果本地测试正常问题在Agent Platform的tool注册缓存执行gcloud alpha generative-ai agents tools update强制刷新。4.3 GKE集群级问题诊断当Cloud Run不够用时如果你的skills需要GPU加速如视频分镜skills、或需访问集群内网服务如连接GKE内部PostgreSQL、或需超长运行时间如自动挖洞skills扫描耗时15分钟就必须迁移到GKE。这时常见问题Pod无法拉取私有镜像在GKE节点上执行docker pull失败原因是没配置Artifact Registry认证。解决方案创建imagePullSecret在Deployment YAML中引用Service无法被Agent Platform访问GKE Service默认是ClusterIP外部不可达。必须创建LoadBalancer类型Service并配置防火墙规则放行tcp:8080Token校验失败GKE Pod内无法解析metadata.google.internal获取Token。解决方案在Pod Security Context中启用automountServiceAccountToken: true并在容器env中注入GOOGLE_APPLICATION_CREDENTIALS指向SA密钥文件需提前创建Secret挂载。我处理过一个分镜skills故障LLM生成{scene_count: 12}但GKE Pod日志显示ValueError: scene_count must be integer between 1 and 10。查了半小时才发现tool spec里scene_count定义为type: integer但LLM生成的是字符串12。解决方案是在Pydantic模型中加Field(coerceTrue)或在tool spec里明确写type: integer, description: Number of scenes as integer, e.g., 5——用e.g., 5强制LLM输出数字而非字符串。4.4 性能瓶颈定位从QPS到冷启动的全链路压测skills不是静态页面它直面LLM的并发洪峰。一次Copilot对话可能触发3-5个skills并行调用。必须压测单实例QPS极限用hey -z 30s -c 10 https://your-service.a.run.app/v1/tools/get_weather:invoke观察Cloud Run监控中的CPU和内存使用率。实测FastAPI单核能扛住约80 QPS超过则出现503冷启动延迟首次调用时Cloud Run从0实例扩容耗时常达2-5秒。解决方案设置最小实例数--min-instances1代价是固定费用LLM等待超时Gemini默认等待tool响应15秒超时即中断。若你的skills如PDF解析必然15秒必须在tool spec中声明functionCallingSpec: {timeout: 30s}服务端实现异步模式立即返回{output: Processing request...}另起goroutine处理完成后用Webhook通知Agent Platform。最后分享一个血泪教训某次上线“codex写论文skills”没做输入长度校验用户传入10MB LaTeX源码导致Cloud Run内存溢出OOMKilled。后来加了max_length10000参数校验再配合content-type: text/plain; charsetutf-8头过滤非文本请求才稳定下来。skills的健壮性永远始于对输入的 paranoid 校验。5. 超越单点技能构建可组合、可演化、可治理的skills生态5.1 技能组合用skills链替代单点调用单个skills解决原子问题但真实场景需要组合。比如“分析竞品官网”需求web_crawler抓取HTML →html_to_text提取正文 →summarize_text生成摘要 →sentiment_analysis判断情感倾向这不能靠LLM硬编排必须用skills orchestration layer。Google Agent Platform原生支持ToolConfig定义调用顺序但更灵活的是用LangGraph自定义from langgraph.graph import StateGraph, END from typing import TypedDict, List class AgentState(TypedDict): url: str html: str text: str summary: str sentiment: str def crawl_node(state: AgentState): state[html] requests.get(state[url]).text return state def extract_node(state: AgentState): state[text] BeautifulSoup(state[html], html.parser).get_text() return state # 构建图 workflow StateGraph(AgentState) workflow.add_node(crawl, crawl_node) workflow.add_node(extract, extract_node) workflow.add_edge(crawl, extract) workflow.set_entry_point(crawl) workflow.set_finish_point(extract)这样LLM只需决定启动crawl后续流程由图引擎驱动。优势在于可视化编排LangGraph UI中间状态可审计每步输出存入Redis单点失败可重试extract失败时crawl结果仍可用。5.2 技能演化版本管理与灰度发布skills不是写完就扔。API变更、模型升级、业务规则调整都会发生。必须支持版本URL路径带版本https://service.v2.a.run.app/v1/tools/get_weather:invoketool spec中声明versionversion: 2.0.0Agent Platform支持多版本共存注册时指定--version2.0.0旧版本仍可用新对话默认用最新版。灰度发布流程部署v2.0.0到新Cloud Run服务注册v2.0.0 tool但不绑定到主Agent用10%内部用户流量路由到v2.0.0通过修改Agent的toolConfig权重监控成功率、延迟、LLM调用准确率无异常后全量切换。5.3 技能治理可观测性与安全审计生产环境skills必须可监控、可审计、可下线可观测性在Cloud Run中启用Stackdriver Logging结构化日志包含tool_name,input_hash,duration_ms,status_code安全审计所有skills调用必须记录user_id从Gemini传来的requestMetadata中提取用于追溯谁触发了什么操作熔断机制当某个skills错误率5%自动降级为返回{output: 服务暂时不可用}避免拖垮整个Agent。最后说个容易被忽略的点skills的文档即代码。每个skills目录下必须有README.md包含功能一句话说明输入参数详解含示例值输出格式说明权限要求如需GCP Storage Reader角色已知限制如最大文本长度、超时时间。这份文档不是给人看的是给LLM看的——Agent Platform会把它作为tool description的一部分喂给Gemini。我见过太多团队skills写得飞起但description写成“调用天气API”结果LLM在用户问“帮我查下iPhone 15发布会日期”时错误调用了weather skills。skills的成败30%在代码70%在描述。写description时把自己当成第一次接触这个能力的LLM问它需要知道什么才能100%正确调用答案就是description的全部内容。