DEV Community

goichi harada
goichi harada

Posted on

AI Agent Skills migrating worldcup rag app(Gemini and GCP to Qwen and Alibaba)

Disclosure

Submission for the Global AI Hackathon Series with Qwen Cloud Blog Post Prize.

Project: CloudPort Agent

Why we did this

Most AI applications today depend on one cloud, one model provider, and one set of SDK assumptions. That is convenient until it is not.

If an American model API suddenly becomes unavailable, rate-limited, too expensive, or difficult to use from a certain region, a working backup matters. Not just a theoretical backup, but a second deployment that can actually run.

That was the motivation behind this project. For the Qwen Cloud hackathon, we took an existing Gemini + Google Cloud application and migrated it to Qwen + Alibaba Cloud. Then we turned the migration knowledge into reusable Qwen Code skills.

The result is CloudPort Agent: a small agent workflow that captures the practical migration traps we hit, so the next migration does not start from a blank page. The migrated app is SoccerScope, a multilingual RAG app for discovering popular World Cup YouTube videos from different countries. Users can search viral football videos across languages and ask the agent to turn the findings into reports, social posts, or website-style summaries.

The Qwen version is a migration demo, not a production-sized deployment. It is meant to prove that the application can run on the Qwen + Alibaba Cloud stack and to document the traps we found along the way.

The app we migrated

The original SoccerScope stack looked like this:

  • Agent framework: Google ADK
  • LLM: Gemini
  • RAG: MongoDB Atlas Vector Search
  • Embeddings: gemini-embedding-001
  • Tools: official MongoDB MCP server
  • Runtime: FastAPI on Google Cloud Run
  • Deployment: Docker container

The target stack was:

  • LLM: Qwen via Alibaba Cloud Model Studio / DashScope
  • Embeddings: text-embedding-v4
  • Runtime: Alibaba Cloud Function Compute 3.0
  • Deployment: Function Compute custom runtime
  • Agent assistant: Qwen Code with custom skills

At first glance, this looked like a relatively easy model swap. Because the app used Google ADK, I expected the model replacement to be mostly configuration and API mapping. In practice, the migration was easier than rewriting the whole app, but it still had several nonobvious traps. Here are the six that mattered.

Trap 1: Account Type

Since the contest was hosted on Qwen Cloud, I initially created an account there. While Qwen Cloud uses email-based authentication, Alibaba Cloud relies on password-based authentication, so I reconfigured my account on Alibaba Cloud. I followed the recommendation to create a corporate account, but please be aware that verification procedures and free credit allowances can differ between corporate and individual accounts.

Since the original application was designed for Cloud Run (Docker), App Engine Service seemed like a suitable choice; however, we opted for Function Compute, which has a proven track record. Although Alibaba Cloud Function Compute supports container execution, the issue was that our specific account type did not allow for a trial period, effectively mandating a production-ready setup. Consequently, we switched to an approach that utilizes the Function Compute 3.0 custom runtime instead of containers (Docker).

That means:

  • Package the Python app as a zip
  • Upload the code package
  • Configure the startup command, such as python3 main.py
  • Expose the expected port
  • Tune the timeout
  • Handle runtime dependencies yourself

This was the first important lesson: a cloud migration agent should not blindly assume that "serverless app" means "container app." The right deployment path depends on the account, the region, the billing setup, and the trial constraints. CloudPort Agent now treats deployment strategy as a decision point instead of hard-coding Docker as the default.

Trap 2: Region choice affects custom domains

We first tried a mainland China region. That immediately created a custom-domain issue. In mainland China regions, binding a custom domain can require ICP filing. For many overseas developers, that is not a realistic quick-start path. The temporary Function Compute URL also was not a good public demo URL for a browser-based web app. In our case, the default URL behavior was not what we wanted for a normal website demo.

The fix was to recreate the deployment in the Tokyo region and use a normal custom-domain setup:

  • Host DNS in Cloudflare
  • Point a subdomain to the Function Compute endpoint
  • Configure the custom domain and HTTPS certificate on the Function Compute side

The migration lesson is simple: choose your region before building everything else. For an agent, this needs to be encoded as a preflight question:

Is this deployment meant to be a public web app with a custom domain?

If yes, region and domain constraints should be checked before writing deployment files.

Trap 3: MCP depends on Node, and Custom Runtime did not have it

SoccerScope uses the official MongoDB MCP server. In the original Docker environment, this was easy because Node.js was already available inside the container. The app could call:

npx mongodb-mcp-server
Enter fullscreen mode Exit fullscreen mode

In Function Compute Custom Runtime, that assumption failed.
There was no Node.js binary on the expected PATH. Even worse, using npx at cold start meant the runtime might try to resolve packages over the network during startup. That is fragile in serverless.

For a while, we removed MCP entirely and fell back to direct pymongo calls. That worked, but it was not a real migration of the original architecture.

The final fix was:

  1. Add Node.js through Function Compute Layers.
  2. Bundle node_modules during build time.
  3. Replace npx mongodb-mcp-server with a direct node invocation.
  4. Avoid network package resolution during cold start.

This is the kind of trap that documentation rarely teaches in one place. Each component is documented somewhere:

  • Function Compute Custom Runtime
  • Function Compute Layers
  • Node.js
  • MCP
  • MongoDB MCP server
  • Serverless cold starts

But the failure only appears when you combine all of them.
That combination is exactly what an agent skill should remember.

Trap 4: Gemini structured output and Qwen structured output are not identical

Gemini made structured output feel simple. In the original app, we could pass a Pydantic-style response schema and expect the API to follow it closely.

When moving to Qwen via an OpenAI-compatible API, the behavior was different. The most important lesson was not "Qwen cannot do structured output." It can. The lesson was:
Do not assume Gemini-style schema behavior will map one-to-one to Qwen. In our migration path, we used JSON output mode and then validated locally with Pydantic.

That exposed several practical issues:

  • The prompt must explicitly say that the model should output JSON.
  • Thinking mode and JSON output settings need to be handled carefully.
  • Truncated output can break JSON, so token limits need extra care.
  • The expected schema should be written directly in the prompt.
  • The application should validate the result locally and retry on mismatch.

So the replacement pattern became:

  1. Put the JSON schema requirements in the prompt.
  2. Ask for JSON explicitly.
  3. Disable thinking where it conflicts with structured output.
  4. Parse the response.
  5. Validate with Pydantic.
  6. Retry if validation fails.

This is boring, but it is robust. CloudPort Agent records this as an API mapping rule rather than leaving the next developer to rediscover it by reading scattered examples.

Trap 5: Embedding migration is not just a model-name change

For RAG, changing the LLM is only half the migration. The embedding model matters just as much. The original app used gemini-embedding-001. The Qwen version uses text-embedding-v4. Those vectors are not interchangeable.

That means the migration must include:

  • Re-embedding the corpus
  • Checking vector dimensions
  • Checking MongoDB Atlas Vector Search index settings
  • Preserving input order
  • Preserving normalization behavior
  • Validating multilingual retrieval quality after the change

In our case, we used 768-dimensional embeddings. But the important rule is: verify the dimension, do not assume it.

There was also a smaller but very practical API difference.
The original Gemini embedding flow used a larger chunk size. For Qwen text-embedding-v4, the official batch size limit is 10 texts per request. So the migration had to change the embedding chunk size to 10.

That is not a difficult change. The problem is that someone has to notice it. This is why CloudPort Agent includes examples such as skills/gemini-to-qwen-api-mapping/examples.md.
The goal is not to make humans memorize every API difference. The goal is to put those small differences into a skill file so the agent can apply them consistently.

Trap 6: The app you migrated may still be calling Gemini

This was the funniest bug and the most embarrassing one.

The Alibaba Cloud deployment succeeded. The web app loaded. The backend was running. Then we ran a search and saw an error like this:

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/...
Enter fullscreen mode Exit fullscreen mode

The app was deployed on Alibaba Cloud, but one remaining code path was still trying to call Gemini. This is where "migration" becomes a dangerous word.

A migration is not complete just because:

  • The app starts
  • The UI loads
  • The new API key is configured
  • Most files were edited

You need an end-to-end smoke test that confirms the actual runtime path. There is one important nuance here.
Because the app uses Google ADK, we cannot say that every Google-related dependency disappeared.

Google ADK itself uses Google's newer google-genai SDK internally. The goal of this migration was not to delete every package with "google" in its name. The goal was to make sure that runtime LLM calls and embedding calls were routed to Qwen / DashScope where intended.

CloudPort Agent now includes verification checks for this kind of hidden coupling. It does not declare success until the migrated app can run a real end-to-end request.

From three days to five minutes

The first manual migration took about three days. After encoding the lessons into Qwen Code skills, the demo migration flow became much shorter.
Measured on this project:

Item Manual migration CloudPort Agent demo
Time About 3 days About 5 minutes
Human approvals Many small decisions 8 approval gates
File edits in the demo scope Manual edits across the repo 2 file edits
Result Running Qwen migration demo Reproducible migration workflow

The point is not that every cloud migration in the world now takes five minutes. The point is that repeated migration knowledge can be captured.

A human still approves important changes. The agent does the mechanical work, applies known API mappings, and checks for common failure patterns. That is the part I find interesting.

Why Qwen Code Skills were a good fit

CloudPort Agent is implemented as Qwen Code custom skills.
A skill is not magic. In practice, it is mostly structured text: instructions, rules, examples, and known traps written in a way the coding agent can use. That simplicity is useful.

The migration knowledge is not locked inside a private service or a complicated framework. It lives in Markdown files. In theory, the same knowledge could also be adapted for Claude Code or other coding agents, although I have not tested that yet.
For this project, the useful part was not just "use an agent." The useful part was:
Put the migration scar tissue into files the agent can read.

Try it

CloudPort Agent is available here:

https://github.com/webbigdata-jp/cloudport-agent

The demo prompt is here:

https://github.com/webbigdata-jp/cloudport-agent/blob/main/docs/demo-prompt.md

The YouTube demo is here:

If you want to try it with Qwen Code on macOS, here is the basic setup.

mkdir qwen-dir
cd qwen-dir
node -v
Enter fullscreen mode Exit fullscreen mode

If Node.js is missing, or if your version is too old:

brew install node@22
brew link --overwrite --force node@22
Enter fullscreen mode Exit fullscreen mode

Install Qwen Code:

brew install qwen-code
Enter fullscreen mode Exit fullscreen mode

Then create an Alibaba Cloud account and get an API key. Please check the latest official Alibaba Cloud / Qwen Code documentation for the current authentication steps.
At the time of writing, the Singapore region has useful free quota options, so it is a good place to start.

Copy CloudPort Agent's skills into your current directory:

git clone https://github.com/webbigdata-jp/cloudport-agent /tmp/cloudport-agent
mkdir -p .qwen
cp -R /tmp/cloudport-agent/.qwen/skills .qwen/
Enter fullscreen mode Exit fullscreen mode

Start Qwen Code:

qwen
Enter fullscreen mode Exit fullscreen mode

Then try a prompt based on:

https://github.com/webbigdata-jp/cloudport-agent/blob/main/docs/demo-prompt.md

Closing

This project started as a simple question:

Can we make a real Gemini + Google Cloud app run on Qwen + Alibaba Cloud?

The answer was yes.
But the more useful result was not the migrated demo site itself. It was the list of small, annoying, migration-specific details that had to be solved along the way.

Docker assumptions. Region assumptions. MCP runtime assumptions. Structured output assumptions.
Embedding batch limits. Hidden Gemini call paths.

Each one is small. Together, they are why real migrations take days. CloudPort Agent is our attempt to turn those small failures into reusable agent skills.

That feels like the right direction for cloud migration: not bigger documentation, but executable memory.

Top comments (0)