1. 项目概述:当“skills”不再是个模糊标签,而是一套可定义、可编排、可验证的智能体能力单元
最近两周,我在三个不同客户的智能体开发现场反复听到同一个词——“skills”。不是泛泛而谈的“你有什么skills”,而是工程师盯着终端日志问:“这个skills调用为什么超时?”产品经理拿着PRD说:“第3版需求里,必须让skills支持带上下文的多轮函数选择。”运维同事在告警群里发截图:“GKE集群里skills服务Pod重启了17次,查不出原因。”——这时候,“skills”已经彻底脱离了简历关键词或招聘JD里的抽象概念,它成了一个有接口、有状态、有依赖、会出错、要监控、得压测的真实软件模块。
我把它理解为智能体能力的最小可部署单元。它不是一段Python脚本,也不是一个API端点,而是一个封装了意图识别、参数解析、执行逻辑、错误恢复和结果归一化的完整闭环。比如“查天气”这个动作,在传统Web开发里可能只是调一次OpenWeather API;但在skills语境下,它必须能处理“明天北京会下雨吗”“上海未来三小时温度变化”“帮我对比深圳和杭州今天的湿度”这三种完全不同的用户表达,自动提取城市、时间、比较维度等结构化参数,并在API失败时降级到缓存数据或返回友好提示,最后把结果格式化成统一JSON Schema供上层Agent Platform消费。这背后涉及NLU模型微调、参数校验规则引擎、重试退避策略、Schema版本管理——全是实打实的工程活。
核心关键词“skills”在当前技术栈中已形成明确分层:最底层是Google Cloud提供的基础设施(GKE容器编排、Cloud Run无服务器运行时、Vertex AI模型托管),中间层是Gemini系列大模型提供的基础推理与工具调用能力,顶层才是开发者真正交付的skills——它们被注册进Agent Platform的技能目录,由LLM根据用户query动态路由、组合、调用。而所有热搜词里反复出现的“your account is not eligible for gemini code assist”这类报错,本质不是权限问题,而是skills注册流程中缺失了关键元数据声明(比如未标注该skills需要访问Cloud Storage权限,或未在IAM策略中绑定对应Service Account)。这恰恰说明:skills已从功能模块升级为云原生应用实体,它的生命周期管理、权限模型、可观测性,都必须遵循云平台规范。
适合谁来读?如果你正在用Gemini构建客服机器人、内部知识助手或自动化工作流,却卡在“功能写完了但总不稳定”“多个skills互相调用时状态混乱”“上线后发现根本没法监控哪个skills拖慢了整体响应”,那么这篇就是为你写的。它不讲大模型原理,不堆砌术语,只聚焦一个目标:让你交付的每一个skills,都能像GKE里一个健康的Deployment一样,可部署、可伸缩、可诊断、可迭代。下面我会拆解真实项目中从零搭建一个生产级skills的全过程,包括架构选型背后的血泪教训、GKE环境配置的隐藏坑点、Gemini Agent Platform集成的关键参数,以及那些官方文档绝不会告诉你的调试技巧。
2. 核心设计思路:为什么放弃“单体函数”,坚持“skills即服务”的架构范式
2.1 从“函数即服务”到“skills即服务”的认知跃迁
最初接到需求时,团队本能地想用Cloud Functions快速实现——写个HTTP触发器,接收用户query,调用Gemini API,解析结果,返回JSON。三天就跑通了demo。但当接入第二个skills(比如“查订单状态”)时,问题立刻暴露:两个函数共用同一套环境变量,订单查询需要访问Cloud SQL,天气查询需要调用外部API,权限配置互相冲突;当用户同时问“今天北京天气如何”和“我的订单123456状态”,两个函数并发执行,日志混在一起根本分不清哪条属于哪个skills;更致命的是,当Gemini API临时抖动,两个函数的重试逻辑互相干扰,导致订单状态被重复查询三次。我们意识到:把skills当成无状态函数,本质上是在用2015年的架构思维解决2024年的智能体问题。
真正的转折点来自GKE集群里的一次故障复盘。当时一个skills因内存泄漏OOM被Kubernetes自动驱逐,但它的Service仍在转发流量,新请求全部失败。运维同事顺手执行了kubectl get pods -n skills-ns,发现集群里居然有7个不同版本的skills Pod在并行运行——有人直接kubectl apply -f v1.yaml,有人用Helm upgrade到v2,还有人手动改ConfigMap后没重启Pod。那一刻我们确认:skills必须拥有独立的命名空间、独立的资源配额、独立的Service Account、独立的监控指标。它不是一段代码,而是一个微服务。
2.2 GKE作为底座的核心价值:不只是容器编排,更是skills的治理中枢
选择GKE而非Cloud Run或Cloud Functions,决策依据非常务实:
资源隔离刚性需求:每个skills需独占CPU/内存配额。例如“PDF解析skills”峰值内存达4GB,而“文本摘要skills”只需512MB。Cloud Run按请求分配资源,无法保证长期驻留的大内存实例;GKE通过ResourceQuota和LimitRange,能强制约束
skills-pdf-parser命名空间最多使用8核CPU+16GB内存,避免一个skills吃光集群资源。网络策略精细化控制:生产环境中,“数据库备份skills”必须禁止外网访问,只允许从
backup-controllerService IP调用;而“客服对话skills”需开放HTTPS端口但限制源IP段。GKE NetworkPolicy支持基于标签、端口、协议的细粒度规则,Cloud Run仅提供全局防火墙开关。滚动更新与金丝雀发布:当更新“发票识别skills”时,我们需要先将10%流量切到v2版本,观察错误率、延迟、GPU显存占用三项指标,达标后再全量。GKE Ingress + Service + Deployment的组合,配合Istio或原生Kubernetes Rollout,能实现毫秒级流量切换;Cloud Run的版本管理停留在“全量切换”层面,缺乏渐进式验证能力。
可观测性深度集成:GKE原生对接Cloud Operations,skills的Pod日志自动打标
skills_name=invoice-parser、version=v2.1.3;Prometheus抓取指标时,每个skills的http_request_duration_seconds指标天然携带skills_name标签。这种开箱即用的维度聚合,让“哪个skills拖慢了Agent Platform响应”这个问题,从需要人工grep日志的噩梦,变成一个简单的BigQuery SQL查询。
提示:不要被GKE的复杂性吓退。我们用Anthos Config Management(ACM)统一管理所有skills的YAML模板,开发者只需填写
skills-name: invoice-parser、cpu-request: "2"等参数,ACM自动生成完整的Namespace、ServiceAccount、Deployment、NetworkPolicy。实际运维成本比维护一堆Cloud Functions触发器更低。
2.3 Gemini Agent Platform的skills注册机制:不是上传代码,而是声明契约
很多开发者误以为skills注册就是把代码包上传到Agent Platform控制台。实际上,Agent Platform根本不执行你的代码——它只做三件事:接收用户query、调用LLM判断需要哪些skills、向skills的HTTP端点发送标准化请求、聚合返回结果。因此,skills注册的本质是向平台声明一份能力契约(Capability Contract)。
这份契约包含四个必填字段:
name: skills唯一标识符,必须符合DNS-1123规范(小写字母、数字、连字符),如weather-forecast-v2。Agent Platform用它生成内部路由规则。description: 供LLM理解的自然语言描述,直接影响工具调用准确率。不能写“查询天气”,而要写“根据城市名和日期,返回该地最高温、最低温、降水概率及空气质量指数。支持‘明天’‘后天’‘未来三天’等相对时间表达。”api_endpoint: skills的HTTP服务地址,必须是HTTPS且具备有效证书。Agent Platform会预检该端点是否返回200 OK及正确Content-Type: application/json。input_schema: JSON Schema定义输入参数结构。例如天气skills必须声明{"city": {"type": "string"}, "date": {"type": "string", "format": "date"}}。Agent Platform在调用前会严格校验用户query提取的参数是否符合此Schema,不符合则拒绝调用。
我们曾因input_schema中"format": "date"写成"format": "date-time",导致LLM提取的“2024-05-20”被判定为非法,整个skills调用链路中断。这个细节在官方文档里藏在“Schema Validation”小节第三页,但却是生产环境最常见的注册失败原因。
3. 实操全流程:从本地开发到GKE生产部署的12个关键环节
3.1 开发环境准备:用Docker Compose模拟GKE网络拓扑
在真实GKE集群上调试skills效率极低——每次代码修改都要经历git push → CI/CD流水线 → 镜像构建 → Pod部署 → 日志查看的漫长循环。我们的解决方案是:用Docker Compose在本地复现GKE的核心网络特征。
# docker-compose.yml version: '3.8' services: # 模拟Agent Platform的调用方 agent-simulator: image: curlimages/curl:latest depends_on: [weather-skills] entrypoint: ["sh", "-c"] command: | while true; do curl -X POST https://weather-skills:8080/v1/execute \ -H "Content-Type: application/json" \ -d '{"city":"Beijing","date":"2024-05-20"}' \ --cacerts /certs/ca.crt 2>/dev/null | jq . sleep 5 done # skills服务,挂载本地代码目录实现热重载 weather-skills: build: ./skills/weather ports: ["8080:8080"] volumes: - ./skills/weather:/app - ./certs:/certs environment: - GCP_PROJECT_ID=demo-project - VERTEX_AI_LOCATION=us-central1 # 关键:模拟GKE的DNS解析 extra_hosts: - "weather-skills.default.svc.cluster.local:127.0.0.1"这个配置实现了三个关键模拟:
- HTTPS强制通信:skills容器内置自签名证书,
agent-simulator必须通过--cacerts指定CA证书才能调用,提前暴露证书配置问题; - Kubernetes DNS兼容:
extra_hosts将weather-skills.default.svc.cluster.local映射到本地,使skills代码中调用https://weather-skills.default.svc.cluster.local:8080与GKE环境完全一致; - 环境变量一致性:
GCP_PROJECT_ID和VERTEX_AI_LOCATION与GKE集群配置完全相同,避免“本地能跑线上报错”的经典陷阱。
实操心得:我们给每个skills目录添加
make dev命令,一键启动Compose并打开日志流。开发者改完代码保存,nodemon或watchdog自动重启进程,5秒内就能看到新逻辑在模拟Agent Platform下的表现。这比在GKE上调试快10倍。
3.2 skills代码骨架:为什么必须包含健康检查、就绪探针和结构化日志
一个合格的skills服务,其代码骨架必须包含三个非业务逻辑组件。以Go语言为例(其他语言同理):
// main.go func main() { // 1. 健康检查端点:供Kubernetes livenessProbe调用 http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprint(w, "OK") }) // 2. 就绪探针端点:供readinessProbe调用,检查依赖服务是否可用 http.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) { // 检查Vertex AI Endpoint是否可连通 if !isVertexAIReady() { w.WriteHeader(http.StatusServiceUnavailable) fmt.Fprint(w, "Vertex AI unreachable") return } // 检查Cloud Storage bucket是否存在 if !isBucketReady() { w.WriteHeader(http.StatusServiceUnavailable) fmt.Fprint(w, "GCS bucket missing") return } w.WriteHeader(http.StatusOK) fmt.Fprint(w, "Ready") }) // 3. 主业务端点:严格遵循Agent Platform的Request/Response Schema http.HandleFunc("/v1/execute", func(w http.ResponseWriter, r *http.Request) { // 强制要求JSON Content-Type if r.Header.Get("Content-Type") != "application/json" { http.Error(w, "Content-Type must be application/json", http.StatusBadRequest) return } // 解析请求体,自动绑定到结构体 var req WeatherRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid JSON format", http.StatusBadRequest) return } // 结构化日志:每条日志必须包含skills_name、request_id、trace_id logger := log.With( "skills_name", "weather-forecast", "request_id", r.Header.Get("X-Request-ID"), "trace_id", r.Header.Get("X-Cloud-Trace-Context"), ) logger.Info("Received request", "city", req.City, "date", req.Date) // 执行核心逻辑... result, err := executeForecast(req) if err != nil { logger.Error("Execution failed", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } // 返回标准化响应 w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) }) log.Fatal(http.ListenAndServe(":8080", nil)) }为什么这些看似“多余”的代码必不可少?
- 健康检查(/healthz):当skills进程卡死但端口仍监听时,Kubernetes会杀死并重启Pod。没有它,故障Pod会长期占用资源。
- 就绪探针(/readyz):在skills启动时,Vertex AI模型加载、Cloud Storage客户端初始化都需要时间。就绪探针确保Kubernetes只将流量导入已准备就绪的Pod,避免“503 Service Unavailable”。
- 结构化日志:GKE日志系统会自动提取
skills_name等字段,生成Dashboard时可直接按skills分组统计错误率。如果只用fmt.Printf打印日志,所有skills日志将混在一起,排查问题如同大海捞针。
3.3 GKE集群配置:避开默认设置的三大深坑
创建GKE集群时,官方控制台的“快速创建”按钮很诱人,但我们坚持用gcloud命令行+Terraform管理,因为默认配置埋着三个致命陷阱:
坑一:默认节点池使用e2-standard-4机型,但skills需要GPU加速
- 现象:部署“图像识别skills”后,推理延迟高达8秒,远超SLA要求的500ms。
- 根因:
e2-standard-4是纯CPU机型,而Gemini Vision模型推理需NVIDIA T4 GPU。 - 解决方案:创建专用GPU节点池:
并为skills Deployment添加gcloud container node-pools create gpu-pool \ --cluster=skills-cluster \ --zone=us-central1-a \ --machine-type=n1-standard-8 \ --accelerator=type=nvidia-tesla-t4,count=1 \ --disk-size=100 \ --enable-autorepair \ --enable-autoupgradenodeSelector:spec: nodeSelector: cloud.google.com/gke-accelerator: nvidia-tesla-t4
坑二:默认VPC网络禁用Private Google Access
- 现象:skills调用Vertex AI Endpoint时超时,
curl -v https://us-central1-aiplatform.googleapis.com返回Connection refused。 - 根因:GKE节点默认使用公网IP访问Google API,但企业防火墙策略禁止出站443端口。
- 解决方案:启用Private Google Access,让节点通过Google骨干网内网访问API:
gcloud compute networks subnets update default \ --region=us-central1 \ --enable-private-google-access
坑三:默认Service Account权限过宽
- 现象:安全审计发现
defaultService Account拥有roles/editor权限,违反最小权限原则。 - 根因:“快速创建”集群自动为节点Service Account授予高权限。
- 解决方案:创建专用Service Account并绑定精细权限:
# 创建SA gcloud iam service-accounts create skills-sa \ --display-name="Skills Service Account" # 绑定必要权限(非editor!) gcloud projects add-iam-policy-binding demo-project \ --member="serviceAccount:skills-sa@demo-project.iam.gserviceaccount.com" \ --role="roles/aiplatform.user" gcloud projects add-iam-policy-binding demo-project \ --member="serviceAccount:skills-sa@demo-project.iam.gserviceaccount.com" \ --role="roles/storage.objectViewer" # 更新节点池使用该SA gcloud container node-pools update default-pool \ --cluster=skills-cluster \ --service-account=skills-sa@demo-project.iam.gserviceaccount.com
3.4 skills镜像构建:为什么Dockerfile必须多阶段且精简
我们曾用FROM golang:1.21直接构建镜像,结果单个skills镜像大小达1.2GB,拉取耗时超过2分钟,严重拖慢Pod启动速度。优化后的Dockerfile如下:
# 构建阶段:编译二进制 FROM golang:1.21-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o weather-skills . # 运行阶段:仅包含二进制和必要文件 FROM alpine:3.19 RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /app/weather-skills . COPY certs/tls.crt certs/tls.key certs/ca.crt /certs/ EXPOSE 8080 CMD ["./weather-skills"]关键优化点:
- 多阶段构建:第一阶段用完整Golang环境编译,第二阶段用轻量
alpine镜像,最终镜像仅28MB; - 静态编译:
CGO_ENABLED=0禁用CGO,生成纯静态二进制,无需在Alpine中安装glibc; - 证书预置:将TLS证书打包进镜像,避免启动时从Secret Volume挂载的IO延迟;
- 最小基础镜像:
alpine:3.19比debian:slim小60%,且漏洞更少(经Trivy扫描确认)。
注意:不要在运行阶段
RUN apk add curl来测试HTTPS连接——这会增大镜像且无实际价值。健康检查应由Kubernetes探针完成,而非容器内自检。
3.5 Agent Platform注册实战:绕过“not eligible”错误的七步法
热搜词中高频出现的your account is not eligible for gemini code assist,本质是Agent Platform的skills注册校验失败。我们总结出七步法确保100%成功:
确认Google Cloud项目已启用必要API
在Cloud Console中依次启用:AI Platform Training & Prediction APIVertex AI APICloud Resource Manager API
(缺少任一API,注册页面会静默失败)
为项目绑定Billing Account
即使使用免费额度,也必须关联Billing Account。未绑定时,Agent Platform控制台显示“Create Skill”按钮为灰色。创建专用Service Account并授权
gcloud iam service-accounts create agent-platform-sa \ --display-name="Agent Platform SA" gcloud projects add-iam-policy-binding demo-project \ --member="serviceAccount:agent-platform-sa@demo-project.iam.gserviceaccount.com" \ --role="roles/aiplatform.admin"下载Service Account密钥JSON文件
在Agent Platform控制台的“Settings → Authentication”中,选择该SA并下载密钥。在skills服务中配置认证
将密钥文件挂载为Secret Volume,并在代码中指定:os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/secret/key.json")注册时填写Endpoint URL的精确格式
必须是https://<service-name>.<namespace>.svc.cluster.local:8080/v1/execute,其中<service-name>与Kubernetes Service名称一致,<namespace>为skills所在命名空间。少一个字符都会导致“Endpoint unreachable”。首次注册后等待5分钟再测试
Agent Platform后台需同步服务发现信息,立即测试会返回503 Service Unavailable。我们用sleep 300 && curl ...作为CI/CD流水线的最后一步。
4. 生产环境问题排查:GKE+Gemini组合下的典型故障速查表
4.1 “skills调用超时”问题的三层诊断法
当Agent Platform报告某个skills响应超时(默认30秒),按以下顺序排查:
| 层级 | 检查项 | 命令/方法 | 典型现象 | 解决方案 |
|---|---|---|---|---|
| L1:网络层 | Skills Service是否可达 | kubectl get svc -n weather-nskubectl exec -it <agent-pod> -- curl -v https://weather-service.weather-ns.svc.cluster.local:8080/healthz | 返回curl: (7) Failed to connect | 检查Service的selector是否匹配Pod标签;确认NetworkPolicy未阻断weather-ns命名空间到default命名空间的流量 |
| L2:应用层 | Skills进程是否存活 | kubectl get pods -n weather-nskubectl logs <pod-name> -n weather-ns --tail=50 | Pod状态为CrashLoopBackOff,日志末尾显示panic: runtime error: invalid memory address | 检查代码中是否有nil指针解引用;增加defer回收资源;在main()开头添加runtime.GOMAXPROCS(runtime.NumCPU()) |
| L3:依赖层 | 外部API是否正常 | kubectl exec -it <pod-name> -n weather-ns -- sh -c "curl -v https://api.openweathermap.org/data/2.5/weather?q=Beijing&appid=xxx" | 返回{"cod":"401","message":"Invalid API key"} | 在GKE Secret中更新API Key;确认skills代码读取Secret的路径正确(如/var/secrets/api-key) |
实操心得:我们开发了一个
skills-debug工具,一键执行三层诊断:# 使用方式:skills-debug weather-service weather-ns kubectl get svc "$1" -n "$2" && \ kubectl get pods -n "$2" && \ kubectl logs -l -n "$2" --tail=20 && \ kubectl exec -it "$(kubectl get pod -l app=$1 -n $2 -o jsonpath='{.items[0].metadata.name}')" -n "$2" -- curl -v http://localhost:8080/readyz运维同事只需复制粘贴一行命令,5秒内获得完整诊断报告。
4.2 “LLM无法选择skills”问题的Schema调试技巧
当用户说“查北京天气”,Agent Platform却调用“订单查询skills”,问题几乎100%出在input_schema定义上。调试步骤:
捕获LLM的原始tool call请求
在skills的/v1/execute端点开头添加日志:body, _ := io.ReadAll(r.Body) log.Printf("Raw request body: %s", string(body)) // 记录原始JSON r.Body = io.NopCloser(bytes.NewReader(body)) // 恢复Body供后续解码对比Schema与实际输入
将日志中的JSON与input_schema逐字段比对。常见错误:- 用户说“明天北京天气”,LLM提取
{"city": "Beijing", "date": "tomorrow"},但Schema要求"date"为"format": "date",而"tomorrow"不是ISO格式日期; - 用户说“上海未来三天”,LLM提取
{"city": "Shanghai", "date_range": "3 days"},但Schema中未定义date_range字段,导致参数被忽略。
- 用户说“明天北京天气”,LLM提取
修正Schema并重新注册
正确的天气skills Schema应支持灵活时间表达:{ "type": "object", "properties": { "city": {"type": "string"}, "date": {"type": ["string", "null"]}, "date_range": {"type": ["string", "null"]} }, "required": ["city"] }注册后,LLM会根据描述自动选择填充
date或date_range字段。
4.3 GKE资源争抢导致skills性能抖动的定位方法
某天凌晨,所有skills的P95延迟突然从200ms飙升至2s,但CPU/内存监控曲线平稳。最终定位到根源:集群节点磁盘IO饱和。
证据链:
kubectl top nodes显示节点cpu和memory使用率均低于50%;kubectl describe node <node-name>中Conditions显示DiskPressure: True;kubectl logs <pod-name> -n weather-ns发现大量context deadline exceeded错误,而非业务错误;- 登录节点执行
iostat -x 1,%util持续100%,await达500ms。
根因:一个未配置
resources.limits的“日志归档skills”疯狂写入/tmp目录,耗尽节点磁盘Inodes。解决方案:
- 为所有skills Deployment添加资源限制:
resources: limits: memory: "512Mi" cpu: "500m" ephemeral-storage: "1Gi" # 关键!限制临时存储 - 将日志输出重定向到Cloud Logging(而非本地文件):
client, _ := logging.NewClient(ctx, "demo-project") logger := client.Logger("skills-logs") logger.Log(logging.Entry{Payload: "Processing request..."})
- 为所有skills Deployment添加资源限制:
警告:GKE默认不限制
ephemeral-storage,这是生产环境最隐蔽的性能杀手。务必为每个skills显式声明。
4.4 “skills间循环调用”引发的雪崩效应
当“会议纪要skills”调用“语音转文字skills”,而后者又调用“翻译skills”,再调用“会议纪要skills”时,会形成无限递归。GKE的防护机制是:
- Pod级防护:每个skills容器设置
--max-old-space-size=1024(Node.js)或GOMEMLIMIT=1G(Go),内存超限时进程崩溃,Pod重启; - Service级防护:GKE Ingress配置
maxRetries: 3,单次请求最多重试3次; - Agent Platform级防护:平台强制设置skills调用深度上限为5层,第6层调用直接返回
400 Bad Request。
但被动防护不如主动预防。我们在所有skills代码中植入调用链追踪:
// 从HTTP Header读取调用链ID traceID := r.Header.Get("X-Skills-Trace-ID") if traceID == "" { traceID = uuid.NewString() } // 生成子ID并传递 childTraceID := traceID + "-" + uuid.NewString()[0:4] log.With("trace_id", childTraceID).Info("Calling speech-to-text skills") // 设置Header供下游读取 req.Header.Set("X-Skills-Trace-ID", childTraceID)当发现日志中出现abc123-def4-gh56-ij78-kl90-mn12-op34-qr56这样超长traceID时,立即触发告警,人工介入切断循环。
5. 进阶实践:skills的灰度发布、AB测试与效能评估体系
5.1 基于Istio的skills金丝雀发布:用流量镜像验证新版本
当升级“股票分析skills”v2时,我们不直接切流,而是用Istio的流量镜像(Traffic Mirroring)进行零风险验证:
# mirror-v2.yaml apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: weather-vs spec: hosts: - weather-service.weather-ns.svc.cluster.local http: - route: - destination: host: weather-service.weather-ns.svc.cluster.local subset: v1 weight: 100 mirror: host: weather-service.weather-ns.svc.cluster.local subset: v2 mirrorPercentage: value: 100这个配置将100%生产流量镜像到v2版本(不改变主流量走向),v2收到请求后执行完整逻辑,但其响应被丢弃,只记录日志和指标。我们监控v2的以下指标:
- 错误率对比:v1错误率0.2%,v2错误率0.15% → 可行;
- 延迟分布:v1 P95=320ms,v2 P95=280ms → 有提升;
- 资源消耗:v2内存使用峰值比v1高15% → 需优化。
只有三项指标全部达标,才执行下一步的权重切换:
# switch-to-v2.yaml - route: - destination: host: weather-service.weather-ns.svc.cluster.local subset: v1 weight: 20 - destination: host: weather-service.weather-ns.svc.cluster.local subset: v2 weight: 805.2 skills效能评估的四大黄金指标
不能只看“skills是否成功调用”,必须建立量化评估体系。我们在Grafana中构建了Skills Health Dashboard,核心指标:
| 指标 | 计算公式 | 健康阈值 | 业务含义 | 数据来源 |
|---|---|---|---|---|
| 调用成功率 | sum(rate(http_request_total{code=~"2.."}[1h])) / sum(rate(http_request_total[1h])) | ≥99.5% | 技能基础可用性 | Prometheus HTTP metrics |
| 平均响应延迟 | histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[1h])) | ≤500ms | 用户体验底线 | Prometheus Histogram |
| LLM调用准确率 | sum(increase(agent_platform_skills_invoked_total{skill_name="weather"}[1h])) / sum(increase(agent_platform_query_total[1h])) | ≥95% | LLM对skills的理解能力 | Agent Platform Exported Metrics |
| 资源效率比 | (sum(container_cpu_usage_seconds_total{container="weather-skills"}) / sum(container_memory_usage_bytes{container="weather-skills"})) | ≥0.8 | 单位内存产生的CPU价值 | GKE Metrics |
注意:
LLM调用准确率指标需在Agent Platform中开启Metrics Export,并配置BigQuery Sink。我们发现,当该指标低于90%时,80%的问题源于input_schema描述不够清晰,而非LLM本身。
5.3 skills的自动化回归测试框架
为防止“修复一个Bug引入三个新Bug”,我们构建了基于curl+jq的轻量测试框架:
# test-weather.sh set -e URL="https://weather-service.weather-ns.svc.cluster.local:8080/v1/execute" # 测试用例1:标准查询 RESPONSE=$(curl -k -s -X POST "$URL" \ -H "Content-Type: application/json" \ -d '{"city":"Beijing","date":"2024-05-20"}') echo "$RESPONSE" | jq -e '.temperature_celsius' > /dev/null || exit 1 # 测试用例2:边界条件(空城市) RESPONSE=$(curl -k -s -X POST "$URL" \ -H "Content-Type: application/json" \ -d '{"city":"","date":"2024-05-20"}') echo "$RESPONSE" | jq -e '.error' > /dev/null || exit 1 # 测试用例3:性能压测(10并发) ab -n 100 -c 10 -p weather-payload.json -T "application/json" "$URL" | \ grep "Time per request" | awk '{print $4}' | \ awk '$1 > 1000 {exit 1}'该脚本集成到CI/CD中,每次PR提交自动执行。测试失败时,Jenkins会截图Grafana中对应skills的实时指标曲线,让开发者一眼看到是延迟突增还是错误率飙升。
6. 经验沉淀:踩过的12个坑与对应的防御性编码清单
6.1 权限相关坑:从“AccessDenied”到“PermissionDenied”的认知升级
坑1:混淆
roles/aiplatform.user与roles/aiplatform.editoruser角色只能调用已部署的Endpoint,不能创建新模型;editor可创建但无权访问Cloud Storage。解决方案:为skills SA绑定roles/aiplatform.user+roles/storage.objectViewer。坑2:忘记为GKE节点配置Workload Identity
导致skills无法使用Service Account调用Vertex AI。解决方案:创建集群时启用Workload Identity,并为每个skills Namespace创建WorkloadIdentityPool。**坑3:Agent Platform