
这是 Qwen Cloud 黑客松的参赛作品,拿了博客文章奖。项目地址在 GitHub 上,有兴趣可以去看看。
写这篇的原因是觉得这个方向挺有意思的——不是单纯把应用跑起来,而是把迁移过程中的踩坑经验固化成可复用的东西。
现在的 AI 应用基本都是一个套路:绑死一个云服务商、一个模型、一个 SDK(通常是官方提供的)。这样开发确实方便,直到出问题时才发现麻烦了。
如果哪天某个模型 API 突然不可用了、限流了、涨价了,或者在某些地区访问困难,一个能真正跑起来的备份方案就很重要了——不是理论上"我们支持多云"的那种备份。
这个项目的初衷就在这儿。借着 Qwen Cloud 黑客松的机会,把一个现有的 Gemini + Google Cloud 应用迁移到了 Qwen + 阿里云,然后把迁移过程中学到的东西整理成 Qwen Code 的自定义 Skills。
最终成果是 CloudPort Agent:一个小型 Agent 工作流,把我们实际踩过的坑都记录下来,下次做类似迁移就不用从零开始了。迁移的对象是 SoccerScope,一个多语言 RAG(检索增强生成)应用,用来搜索不同国家的热门世界杯 YouTube 视频,支持跨语言搜索,还能把结果整理成报告、社交帖子或者网站风格的摘要。
需要说明的是,Qwen 版本是迁移演示,不是生产级部署。它的目的是验证应用能否在 Qwen + 阿里云技术栈上正常运行,同时记录我们发现的那些坑。
原始 SoccerScope 的技术栈是这样的:
目标技术栈:
乍一看,这个迁移主要是换个模型,应该比较简单。应用用了 Google ADK,我以为主要就是配置和 API 映射的工作。实际操作下来,迁移确实比重写整个应用容易,但还是有几个意想不到的坑。下面说说最重要的六个。
因为黑客松托管在 Qwen Cloud,我先在那里创建了账号。Qwen Cloud 用邮箱登录,但阿里云用的是密码登录,于是在阿里云上重新注册了账号。建议创建企业账号,但要注意企业账号和个人账号的验证流程和免费额度可能不一样。
原应用设计跑在 Cloud Run(用 Docker),所以 App Engine Service 看起来是个合适的选择;但最终我们选了函数计算,虽然阿里云函数计算支持容器执行,但问题是我们的账户类型不支持试用,等于强制要求生产级别的配置。
最后换成了函数计算 3.0 自定义运行时方案,不用容器了。具体做法:
这是第一个重要教训:云迁移 Agent 不能盲目假设"无服务器应用"就等于"容器应用"。正确的部署策略取决于账户类型、区域、计费设置和试用限制。CloudPort Agent 现在把部署策略当作决策点,而不是默认用 Docker。
我们一开始选了大陆区域,很快就遇到了自定义域名的问题。在大陆区域,绑定自定义域名可能需要 ICP 备案,这对很多海外开发者来说不是一条现实的快速启动路径。函数计算的临时 URL 也不适合作为公开演示的 URL,对基于浏览器的 Web 应用来说不太合适。
解决方案是在东京区域重新部署,用正常的方式配置自定义域名:
迁移教训很简单:选区域要在搭建一切之前。对于 Agent 来说,这需要作为一个预检问题来编码:
这个部署是否需要作为公开 Web 应用使用自定义域名?
如果需要,在写部署文件之前就应该检查区域和域名限制。
SoccerScope 用的是官方 MongoDB MCP 服务器。在原来 Docker 环境里,Node.js 已经在容器里可用了,应用可以直接调用 npx mongodb-mcp-server。
但在函数计算自定义运行时里,这个假设不成立。Node.js 不在预期的 PATH 上。更糟糕的是,冷启动时用 npx 会尝试联网解析包,这在无服务器环境下很脆弱。
有一段时间我们直接去掉了 MCP,回退到直接调用 pymongo。这样能跑,但并不是真正迁移了原始架构。
最终解决方案:
这是那种文档很少在一个地方讲清楚的问题。每个组件都有文档:函数计算自定义运行时、函数计算 Layers、Node.js、MCP、MongoDB MCP 服务器、无服务器冷启动。但失败只有在你把所有这些组合起来时才会出现。这种组合恰恰是 Agent Skill 应该记住的东西。
Gemini 让结构化输出感觉很简单。在原应用里,可以传一个 Pydantic 风格的响应模型,API 基本上能严格遵循。
切换到通过 OpenAI 兼容 API 调用 Qwen 时,行为就不一样了。最重要的教训不是说"Qwen 做不了结构化输出"——它可以。教训是:不要假设 Gemini 风格的结构化输出能一对一映射到 Qwen。在我们的迁移路径中,用了 JSON 输出模式,然后用 Pydantic 在本地验证。
这暴露了几个实际问题:
替换模式变成了:
这很无聊,但很稳健。CloudPort Agent 把这记录为 API 映射规则,而不是让下一个开发者通过阅读零散的例子来重新发现。
对于 RAG 来说,换 LLM 只是迁移的一半。Embedding 模型同样重要。原应用用 gemini-embedding-001,Qwen 版本用 text-embedding-v4。这些向量不能互换。
这意味着迁移必须包括:
我们用的是 768 维 Embedding。但重要规则是:验证维度,不要假设。
还有一个较小但很实际的 API 差异。原 Gemini Embedding 流程用了较大的 chunk size。对于 Qwen text-embedding-v4,官方 batch 限制是每请求 10 条文本。所以迁移必须把 Embedding chunk size 改成 10。
这个改动不难。问题是要有人注意到它。这就是为什么 CloudPort Agent 包含了类似 skills/gemini-to-qwen-api-mapping/examples.md 这样的例子。目标不是让人类记住每个 API 差异,而是把这些小差异放到 Skill 文件里,让 Agent 能够一致地应用。
这是最有趣也最尴尬的 bug。
阿里云部署成功了。Web 应用加载了。后端在运行。然后我们执行了一次搜索,看到了这样的错误:
Generation failed: agent error: No API key was provided.
Please pass a valid API key.
Learn how to create an API key at https://ai.google.dev/...
应用部署在阿里云,但某段代码路径还在尝试调用 Gemini。这就是"迁移"变成危险词汇的地方。
迁移没有完成不是因为:
你需要端到端的冒烟测试来确认实际的运行时路径。这里有一个重要的细节:因为应用用了 Google ADK,不能说每个带"google"的依赖都消失了。Google ADK 内部使用 Google 较新的 google-genai SDK。迁移的目标不是删除每个包名里带"google"的东西。目标是确保运行时 LLM 调用和 Embedding 调用都路由到 Qwen / DashScope。
CloudPort Agent 现在包含了针对这种隐藏耦合的验证检查。在迁移后的应用能够运行真实的端到端请求之前,它不会宣告成功。
第一次手动迁移花了大约三天。把经验编码成 Qwen Code Skills 后,演示迁移流程变得短多了。在这个项目上测量的结果:
| 项目 | 手动迁移 | CloudPort Agent 演示 |
|---|---|---|
| 时间 | 大约 3 天 | 大约 5 分钟 |
| 人工审批 | 大量小决策 | 8 个审批关卡 |
| 演示范围内的文件编辑 | 跨仓库手动编辑 | 2 个文件编辑 |
| 结果 | 运行中的 Qwen 迁移演示 | 可复现的迁移工作流 |
重点不是世界上每个云迁移现在都只需要五分钟。重点是重复的迁移知识可以被捕获。
人类仍然审批重要变更。Agent 做机械性的工作,应用已知的 API 映射,检查常见失败模式。这部分我觉得有意思。
CloudPort Agent 是用 Qwen Code 自定义 Skills 实现的。Skill 不是什么魔法。实际上它主要是结构化的文本:用 Agent 能理解的方式写成的指令、规则、例子和已知坑。这种简单性很有用。
迁移知识没有锁在私有服务或复杂框架里。它存在于 Markdown 文件里。理论上,同样的知识也可以适配到 Claude Code 或其他编码 Agent,虽然我还没测试过。
对这个项目来说,有用的不只是"用 Agent"。有用的部分是:把迁移中踩过的坑整理成 Agent 能读取的文件。
CloudPort Agent 在这里。
演示 prompt 在这里。
YouTube 演示在这里。
如果想在 macOS 上用 Qwen Code 试试,基本步骤:
mkdir qwen-dir
cd qwen-dir
node -v
如果 Node.js 缺失或版本太旧:
brew install node@22
brew link --overwrite --force node@22
安装 Qwen Code:
brew install qwen-code
然后创建阿里云账号并获取 API key。请查看最新的阿里云 / Qwen Code 官方文档了解当前的身份验证步骤。写这篇文章时,新加坡区域有不错的免费配额,是个好的起点。
把 CloudPort Agent 的 Skills 复制到当前目录:
git clone https://github.com/webbigdata-jp/cloudport-agent /tmp/cloudport-agent
mkdir -p .qwen
cp -R /tmp/cloudport-agent/.qwen/skills .qwen/
启动 Qwen Code:
qwen
然后试试基于演示 prompt 的指令。
这个项目起源于一个简单的问题:能不能让一个真实的 Gemini + Google Cloud 应用跑在 Qwen + 阿里云上?
答案是能。但更有用的结果不是迁移后的演示站本身,而是迁移过程中不得不解决的那些小而烦人的、迁移特有的细节。
Docker 假设。区域假设。MCP 运行时假设。结构化输出假设。Embedding batch 限制。隐藏的 Gemini 调用路径。
每一个单独看都很小。加在一起,就是为什么真正的迁移需要好几天。CloudPort Agent 是我们把这些小失败变成可复用 Agent Skills 的尝试。
这才是云迁移的正确方向:不是更大的文档,而是可执行的记忆。