
Migraq
- 8 installs
- 33 repo stars
- Updated April 26, 2026
- bighardperson/computer-science-skills-collection
migraq is a Claude skill for Tencent Cloud migration (CMG/MSP) that scans cross-cloud resources, matches specs to Tencent Cloud, and runs TCO analysis and migration planning.
About
migraq is a Tencent Cloud migration assistant covering the CMG/MSP workflow. It scans resources across AWS, Alibaba Cloud, Huawei Cloud, and GCP, maps source specs to equivalent Tencent Cloud specs, runs TCO cost analysis, and helps plan migration cutovers. A developer uses it to inventory another cloud, get spec recommendations, and produce migration quotes. It calls the CMG ChatCompletions API using Tencent Cloud AK/SK credentials with TC3-HMAC-SHA256 signing.
- Tencent Cloud migration expert (CMG/MSP) for cross-cloud resource scanning and planning
- Scans AWS, Alibaba Cloud, Huawei Cloud, and GCP resources and maps specs to Tencent Cloud equivalents
- TCO cost analysis and migration quotes via the CMG ChatCompletions API with TC3-HMAC-SHA256 auth
Migraq by the numbers
- 8 all-time installs (skills.sh)
- Ranked #862 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
migraq capabilities & compatibility
Requires Tencent Cloud AK/SK; calls the CMG ChatCompletions API.
- Capabilities
- cloud resource scan · spec matching · tco analysis · migration planning
- Works with
- aws · gcp
- Use cases
- devops · research
- Pricing
- Bring your own API key
What migraq says it does
腾讯云迁移服务专家,支持跨云资源扫描、选型推荐、TCO 分析与迁移方案规划
跨云资源扫描**:盘点 AWS、阿里云、华为云、GCP 等云上资源清单
使用腾讯云 AK/SK 鉴权,通过环境变量配置密钥
npx skills add https://github.com/bighardperson/computer-science-skills-collection --skill migraqAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 33 |
| Last updated | April 26, 2026 |
| Repository | bighardperson/computer-science-skills-collection ↗ |
What it does
Scan cross-cloud resources, map specs to Tencent Cloud, and run TCO analysis for cloud migration planning.
Who is it for?
Teams planning a migration to Tencent Cloud from AWS, Alibaba Cloud, Huawei Cloud, or GCP.
Skip if: Migrations that do not target Tencent Cloud, or users without Tencent Cloud AK/SK.
When should I use this skill?
You need cross-cloud resource scanning, Tencent Cloud spec recommendations, TCO analysis, or migration planning.
What you get
The agent returns a resource inventory, Tencent Cloud spec mapping, and a TCO/migration quote.
- cross-cloud resource inventory
- Tencent Cloud spec recommendations
- TCO/migration quote
By the numbers
- 4 source clouds supported (AWS, Alibaba, Huawei, GCP)
- 1 exposed API interface (MigraQChatCompletions)
Files
MigraQ — 腾讯云迁移服务专家
零、自我介绍
以下场景必须使用固定介绍内容回答,且每次对话只介绍一次,后续轮次不重复:
- 用户主动询问"你是谁"、"能做什么"等身份相关问题时
- 每次对话的第一次 API 调用前(调用前先介绍,再转发问题)
你好,我是 MigraQ — 腾讯云迁移服务专家!
>
我能帮你:
🔍 跨云资源扫描:盘点 AWS、阿里云、华为云、GCP 等云上资源清单
📐 目标规格对标:将源云资源精准映射为腾讯云等效规格
💰 TCO 成本分析:计算迁移前后总拥有成本,输出迁移报价
🗺️ 迁移方案规划:制定割接方案、灰度切流、验收标准
🛠️ 工具选择指引:go2tencentcloud、DTS、COS Migration 等工具使用指南
>
MigraQ: 迁上腾讯云,更简单!
---
核心能力:通过腾讯云 TC3-HMAC-SHA256 签名鉴权调用 CMG ChatCompletions API,将云迁移问题转发给专业迁移 Agent 处理。
---
一、鉴权方式
使用腾讯云 AK/SK 鉴权,通过环境变量配置密钥:
1.1 必填环境变量
TENCENTCLOUD_SECRET_ID— 腾讯云 SecretId(必填)TENCENTCLOUD_SECRET_KEY— 腾讯云 SecretKey,通过 TC3-HMAC-SHA256 签名鉴权(必填)
API 地址和 Region 已内置(cmg.ai.tencentcloudapi.com,ap-shanghai),无需配置。
可选:通过CMG_REGION环境变量覆盖地域,默认ap-shanghai。
密钥获取地址:https://console.cloud.tencent.com/cam/capi
安全建议:建议在 CAM 控制台创建最小权限子账号,仅授予迁移所需 API 权限,避免使用主账号 AK/SK。
环境变量配置方式(推荐:持久化方案)
⚠️ 重要:直接在终端执行 export 仅对当前 shell 会话生效,重启终端后即失效。推荐使用持久化方案,确保每次启动均自动加载密钥,无需重复配置。当检测到用户未配置 AK/SK 时,必须按以下步骤引导用户操作:
步骤一:写入 shell 配置文件
Linux / macOS(写入 ~/.zshrc):
echo 'export TENCENTCLOUD_SECRET_ID="your-secret-id"' >> ~/.zshrc
echo 'export TENCENTCLOUD_SECRET_KEY="your-secret-key"' >> ~/.zshrcWindows PowerShell(写入用户级环境变量,永久生效):
[Environment]::SetEnvironmentVariable("TENCENTCLOUD_SECRET_ID", "your-secret-id", "User")
[Environment]::SetEnvironmentVariable("TENCENTCLOUD_SECRET_KEY", "your-secret-key", "User")步骤二:使配置立即生效
Linux / macOS:
source ~/.zshrcWindows:关闭并重新打开 PowerShell 窗口。
步骤三:验证配置
执行 source ~/.zshrc 后,环境变量立即在当前终端生效,无需重启 AI 工具。可直接运行环境检测脚本验证:
python3 {baseDir}/scripts/check_env.py安全提示:密钥以明文写入 ~/.zshrc,请确保不要将该文件提交到 Git 仓库。---
二、前置检查(初始化工作流)
每次操作前必须先执行环境检测。
2.1 运行环境检测
python3 {baseDir}/scripts/check_env.py脚本依次执行以下检测: 1. 检查 Python 版本(需要 3.7+) 2. 检查 Skill 版本更新(读取本地 SKILL.md front matter 版本,与远端对比) 3. 检查 AK/SK 配置(TENCENTCLOUD_SECRET_ID / TENCENTCLOUD_SECRET_KEY) 4. 验证 CMG API 连通性(cmg.ai.tencentcloudapi.com,TC3 签名)
根据返回码判断状态:
0= 环境就绪,可以正常使用1= Python 版本不满足要求 → 提示用户升级 Python2= AK/SK 未配置 → 提示用户配置密钥3= Gateway 连通失败 → 提示用户检查网络
脚本在退出前会输出一行结构化 JSON 摘要,必须解析此 JSON 判断是否有版本更新:
{"status": "ready"}若有新版本可用,JSON 中会包含 update_available: true:
{"status": "ready", "update_available": true, "local_version": "1.0.8", "remote_version": "1.0.5"}版本更新处理规则:当 JSON 摘要中 update_available 为 true 时,必须在回复用户前主动提示:
💡 MigraQ 有新版本可用(当前 {local_version},最新 {remote_version}),可前往 SkillHub 更新。
提示完成后继续正常执行用户请求,不阻断流程。
2.2 静默模式(供脚本内部调用)
python3 {baseDir}/scripts/check_env.py --quiet静默模式下仅输出错误信息,适合其他脚本调用获取环境状态。
2.3 跳过版本检查
python3 {baseDir}/scripts/check_env.py --skip-update---
三、API 调用方式
3.1 SSE 流式接口(MigraQChatCompletions)
MigraQChatCompletions 为 SSE 流式接口,使用独立调用脚本:
python3 {baseDir}/scripts/migrateq_sse_api.py '<question>' [session_id]question:用户问题(必填,保留原意)session_id:会话 ID(可选,不传则自动生成新的 UUID v4)
示例:
python3 {baseDir}/scripts/migrateq_sse_api.py '阿里云50台ECS如何迁移?'
python3 {baseDir}/scripts/migrateq_sse_api.py '详细说说 go2tencentcloud 步骤' '550e8400-e29b-41d4-a716-446655440000'Dry-run 模式(仅打印签名请求头,不发送请求,用于调试鉴权):
python3 {baseDir}/scripts/migrateq_sse_api.py --dry-run '测试问题'默认调用规则
当用户问题没有明确匹配到特定操作的触发词时,默认使用 MigraQChatCompletions。包括:跨云迁移咨询、资源扫描、选型推荐、TCO 分析、迁移工具指引,以及用户问题含义模糊无法确定具体操作时。
3.2 SessionID 管理
SessionID 控制多轮对话上下文。当前对话中 SessionID 必须保持不变。
| 场景 | SessionID 处理 |
|---|---|
| 首次对话 | 不传 session_id,脚本自动生成 |
| 同一对话追问 | 必须沿用上次返回的 session_id |
| 用户要求新对话 / 重新开始 | 不传 session_id,重新生成,并调用 --clear-session |
# 清除服务端 session(用户要求重新开始时)
python3 {baseDir}/scripts/migrateq_sse_api.py --clear-session⚠️ 关键:SessionID 一旦改变,服务端视为全新对话,不包含任何历史上下文。
---
四、可用接口(当前 1 个)
| 接口 | 说明 | 触发词 | 文档 |
|---|---|---|---|
MigraQChatCompletions | 迁移专家全局对话(SSE 流式) | 默认接口:迁移咨询、资源扫描、选型推荐、TCO、无明确匹配时 | {baseDir}/references/api/MigraQChatCompletions.md |
使用接口前,必须先加载对应接口文档获取参数、返回值和展示规则等详细信息。
---
五、统一输出格式
所有接口调用的输出均为统一 JSON 格式,通过 success 字段区分成功与失败。
成功响应
{
"success": true,
"action": "MigraQChatCompletions",
"data": {
"content": "完整回答内容(Markdown 格式)",
"is_final": true,
"session_id": "uuid-xxx",
"usage": {"prompt_tokens": 100, "completion_tokens": 200, "total_tokens": 300}
},
"requestId": "resp_xxx"
}失败响应
{
"success": false,
"action": "MigraQChatCompletions",
"error": {
"code": "NetworkError",
"message": "无法连接 MigraQ Gateway"
},
"requestId": ""
}响应处理规则
- 将流式输出直接呈现给用户,无需额外包装
- 若
success: false或脚本退出码非 0,告知用户 MigraQ 服务暂时不可用,建议:
1. 运行 python3 {baseDir}/scripts/check_env.py 检查环境 2. 检查 TENCENTCLOUD_SECRET_KEY 是否有效 3. 检查网络是否可以访问 https://cmg.ai.tencentcloudapi.com
常见错误码
| 错误码 | 含义 | 处理方式 |
|---|---|---|
AuthError | 鉴权失败(AK/SK 无效或签名错误) | 提示用户检查 TENCENTCLOUD_SECRET_ID / TENCENTCLOUD_SECRET_KEY 是否正确,不重试 |
NetworkError | 无法连接 CMG API | 检查网络,确保可达 https://cmg.ai.tencentcloudapi.com |
HTTPError | Gateway 返回其他 HTTP 错误 | 检查 Gateway 状态,可稍后重试 |
MissingParameter | 脚本调用缺少参数 | 检查调用方式 |
---
六、注意事项
1. 密钥安全:严禁将 AK/SK 硬编码在代码中,必须通过环境变量传入 2. 环境变量持久化:AK/SK 必须写入 shell 配置文件(~/.bashrc 或 ~/.zshrc),export 仅对当前会话生效 3. SessionID 管理:同一对话全程使用同一个 SessionID,新对话时不传 session_id 让脚本重新生成 4. SSE 超时:MigraQChatCompletions 为 SSE 流式请求,默认超时 600 秒(10 分钟) 5. 必须等待脚本完整返回:调用 migrateq_sse_api.py 后,必须等待脚本进程退出并输出最终 JSON 结果,期间远端专家 Agent 可能需要较长思考时间(数十秒至数分钟),脚本会通过 stderr 输出等待进度提示。严禁在脚本未返回结果前自行生成回答或中途处理,否则会绕过专业迁移 Agent,导致回答质量下降。 6. 跨平台支持:所有脚本均使用纯 Python 实现,支持 Windows / Linux / macOS,无需 curl、openssl、jq 等外部依赖 7. 默认路由:用户问题没有明确匹配到特定接口触发词时,默认走 MigraQChatCompletions 全局对话
---
七、安全与权限声明
7.1 所需凭证
| 环境变量 | 必填 | 说明 |
|---|---|---|
TENCENTCLOUD_SECRET_ID | 是 | 腾讯云 API SecretId(建议使用子账号) |
TENCENTCLOUD_SECRET_KEY | 是 | 腾讯云 API SecretKey,通过 TC3-HMAC-SHA256 签名传递 |
7.2 数据安全
- 密钥处理:AK/SK 仅通过环境变量读取,通过 HTTP header 传输,不写入任何文件或日志
- 最小权限:建议在 CAM 控制台创建子账号并仅授予迁移所需权限,避免使用主账号 AK/SK
- 网络访问:仅连接内置 CMG API 地址
https://cmg.ai.tencentcloudapi.com - SSL 验证:HTTPS 请求启用完整证书验证(HTTP 地址不验证)
- 无持久化存储:本 Skill 不在本地持久化存储任何用户数据或凭证
7.3 API 操作声明
| 操作 | 类型 | 说明 |
|---|---|---|
ChatCompletions | 只读对话 | 发送问题,获取迁移专家回答(TC3 签名,SSE 流式) |
7.4 版本号维护说明
版本号存在两处,每次升级必须同步修改,否则 check_env.py 显示的版本与 SkillHub 平台不一致:
| 文件 | 字段 | 读取方 |
|---|---|---|
SKILL.md front matter | version | check_env.py 版本自检 |
_skillhub_meta.json | version | SkillHub 平台安装/更新管理 |
工具说明
本技能使用以下工具:
- Write: 写入和创建文件
- Grep: 搜索文件内容
{
"name": "MigraQ",
"installedAt": 1776152039285,
"source": "marketplace",
"iconSource": "migraq",
"version": "1.0.10"
}MigraQChatCompletions — 迁移专家全局对话
腾讯云迁移服务专家对话接口(SSE 流式输出),支持云资源扫描、迁移方案规划、目标云选型推荐、TCO 分析、迁移工具选择等全流程迁移问答。
参数
| 参数 | 必选 | 类型 | 描述 |
|---|---|---|---|
| Input | 是 | String | 用户问题,如 阿里云50台ECS如何迁移到腾讯云 |
| Stream | 是 | Boolean | 固定值 true(SSE 流式输出) |
| SessionKey | 是 | String | 会话 ID(UUID v4)。首次调用由脚本自动生成;多轮对话时传入上次返回的 session_id,服务端据此维护独立的对话上下文 |
鉴权
使用腾讯云 AK/SK 鉴权:Authorization: Bearer <TENCENTCLOUD_SECRET_KEY>
调用示例
python3 {baseDir}/scripts/migrateq_sse_api.py '阿里云50台ECS如何迁移?'
python3 {baseDir}/scripts/migrateq_sse_api.py '详细说说 go2tencentcloud 步骤' '550e8400-e29b-41d4-a716-446655440000'返回格式(脚本输出)
脚本自动解析 SSE 流并汇总为统一 JSON:
{
"success": true,
"action": "MigraQChatCompletions",
"data": {
"content": "## 迁移方案概述\n\n对于阿里云50台ECS迁移到腾讯云...",
"is_final": true,
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"usage": {
"prompt_tokens": 13080,
"completion_tokens": 512,
"total_tokens": 13592
}
},
"requestId": "resp_84ced3ce-1234-5678-abcd-ef0123456789"
}返回字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
content | String | Markdown 格式回答,直接展示给用户 |
is_final | Boolean | 是否为最终结果(固定 true) |
session_id | String | 调用方传入的 SessionID,原样返回 |
usage.prompt_tokens | Integer | 输入 Token 数 |
usage.completion_tokens | Integer | 输出 Token 数 |
usage.total_tokens | Integer | 总 Token 数 |
requestId | String | Gateway 请求 ID,用于问题排查 |
原始 SSE 流格式
event: run.started
data: {"type":"run.started","session_id":"550e8400-e29b-41d4-a716-446655440000"}
event: run.progress
data: {"type":"run.progress","stage":"preparing","summary":"Preparing request"}
event: message.delta
data: {"type":"message.delta","delta":"## 迁移"}
event: message.delta
data: {"type":"message.delta","delta":"方案"}
event: message.completed
data: {"type":"message.completed","reply":"## 迁移方案\n...","usage":{"prompt_tokens":100,"completion_tokens":50,"total_tokens":150}}| SSE 事件 | 含义 |
|---|---|
run.started | 会话已建立,服务端确认收到 SessionKey |
run.progress | 处理进度提示(可忽略) |
message.delta | 流式文本增量,取 delta 字段实时拼接 |
message.completed | 流结束,取 reply(完整回复)和 usage |
Session 管理
服务端按 `SessionKey` 隔离对话上下文,当前对话中 SessionKey 必须保持不变。
| 场景 | 处理方式 |
|---|---|
| 首次对话 | 不传 session_id,脚本自动生成 UUID v4 并通过 SessionKey 发送 |
| 同一对话追问 | 必须沿用上次返回的 session_id,通过 SessionKey 发送 |
| 用户要求新对话 | 不传 session_id,脚本重新生成新 UUID,服务端创建独立上下文 |
SessionKey 通过请求体传入 Gateway,服务端按此字段维护独立的多轮对话上下文。session_id完全由客户端生成和维护,返回结果中的session_id就是客户端传入的值。
清除会话
python3 {baseDir}/scripts/migrateq_sse_api.py --clear-session默认调用规则
当用户问题没有明确匹配到其他触发词时,默认使用 MigraQChatCompletions。包括但不限于:
- 跨云迁移方案咨询
- 云资源扫描和清单生成
- 目标云选型和规格对标
- TCO 成本分析
- 迁移风险评估
- 迁移工具和步骤指引
- 用户问题含义模糊无法确定具体操作时
展示规则
content为 Markdown 格式,可直接展示给用户- 若
success: false,向用户说明服务暂时不可用,并提示检查环境配置
常见错误码
| 错误码 | 含义 | 处理方式 |
|---|---|---|
NetworkError | 无法连接 Gateway | 检查 Gateway 地址和网络 |
HTTPError | Gateway 返回 HTTP 错误 | 检查 TENCENTCLOUD_SECRET_KEY 和 Gateway 状态 |
MissingParameter | 脚本调用缺少参数 | 检查调用方式 |
#!/usr/bin/env python3
"""
MigraQ 环境检测脚本(只读,不修改任何配置)
功能:检测 Python 版本、Skill 版本更新(含 changelog)、AK/SK 配置、Gateway 连通性
用法:
python3 check_env.py # 标准模式:输出详细检测结果
python3 check_env.py --quiet # 静默模式:仅输出错误信息
python3 check_env.py --skip-update # 跳过版本更新检查
返回码:
0 - 环境就绪(AK/SK + Gateway 全部正常)
1 - Python 版本不满足(需要 3.7+)
2 - AK/SK 未配置或鉴权失败
3 - Gateway 连通性失败
4 - Skill 版本过旧,需要更新(可用 --skip-update 跳过强制检查)
跨平台支持: Windows / Linux / macOS
"""
import json
import os
import platform
import sys
from datetime import datetime, timezone
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
SCRIPTS_DIR = SCRIPT_DIR / "scripts"
sys.path.insert(0, str(SCRIPTS_DIR))
DEFAULT_GATEWAY_URL = "https://cmg.ai.tencentcloudapi.com"
DEFAULT_REGION = "ap-shanghai"
_SERVICE = "cmg"
_VERSION = "2024-10-15"
_ACTION = "ChatCompletions"
# 版本检查配置
#
# 版本号存在两处,必须保持同步:
# 1. SKILL.md front matter 中的 `version` 字段 ← check_env.py 读取此处做版本对比
# 2. _skillhub_meta.json 中的 `version` 字段 ← SkillHub 平台读取此处做安装/更新管理
#
# 每次升级版本时,两处都必须同步修改,否则会出现 check_env 版本与平台显示版本不一致的问题。
SKILL_MD_FILE = SCRIPT_DIR.parent / "SKILL.md"
VERSION_CHECK_TIMEOUT = 15 # 秒
VERSION_CHECK_URL = "https://msp.cloud.tencent.com/skill/version"
# ============== 输出控制 ==============
QUIET_MODE = "--quiet" in sys.argv
SKIP_UPDATE = "--skip-update" in sys.argv
def log_info(msg: str):
if not QUIET_MODE:
print(msg)
def log_ok(msg: str):
if not QUIET_MODE:
print(f" [OK] {msg}")
def log_warn(msg: str):
if not QUIET_MODE:
print(f" [WARN] {msg}")
def log_fail(msg: str):
print(f" [FAIL] {msg}")
def log_section(title: str):
if not QUIET_MODE:
print(f"\n=== {title} ===")
# ============== 版本检查 ==============
def parse_version(version_str: str) -> tuple:
"""解析语义化版本号,如 '1.0.0' -> (1, 0, 0)"""
try:
parts = version_str.strip().lstrip("v").split(".")
return tuple(int(p) for p in parts)
except (ValueError, AttributeError):
return (0, 0, 0)
def get_local_version():
"""从 SKILL.md front matter 读取本地版本号,返回 (name, version_str) 或 (None, None)。
注意:本地版本读取自 SKILL.md,而非 _skillhub_meta.json。
_skillhub_meta.json 的 version 字段由 SkillHub 平台独立读取,两者需手动保持一致。
解析 YAML front matter(--- 包裹的头部),提取 name 和 version 字段。
使用简单的行扫描,无需依赖 PyYAML。
"""
if not SKILL_MD_FILE.exists():
return None, None
try:
text = SKILL_MD_FILE.read_text(encoding="utf-8")
lines = text.splitlines()
# 必须以 --- 开头才是 front matter
if not lines or lines[0].strip() != "---":
return None, None
name, version = None, None
for line in lines[1:]:
if line.strip() == "---":
break
if line.startswith("name:"):
name = line.split(":", 1)[1].strip().strip('"').strip("'")
elif line.startswith("version:"):
version = line.split(":", 1)[1].strip().strip('"').strip("'")
return name, version
except IOError:
return None, None
def get_remote_version(name: str):
"""从 SkillHub 查询最新 Skill 版本,返回 (version_str, raw_data) 或 (None, None)
接口: GET https://msp.cloud.tencent.com/skill/version
响应: {"Response": {"Data": {"Version": "x.y.z"}}}
"""
try:
from urllib.request import urlopen, Request as _Req
import ssl
req = _Req(VERSION_CHECK_URL, headers={"Accept": "application/json"})
ctx = ssl.create_default_context()
with urlopen(req, context=ctx, timeout=VERSION_CHECK_TIMEOUT) as resp:
data = json.loads(resp.read().decode("utf-8"))
version = data.get("Response", {}).get("Data", {}).get("Version")
return version, data
except Exception:
return None, None
def check_version_update() -> dict:
"""检查本地版本与远端版本是否一致"""
name, local_ver = get_local_version()
if not name or not local_ver:
return {
"status": "no_meta",
"message": "未找到 _skillhub_meta.json 或版本信息缺失",
}
remote_ver, remote_data = get_remote_version(name)
if not remote_ver:
return {
"status": "check_failed",
"local_version": local_ver,
"message": "无法获取远端版本信息(网络问题或接口不可用)",
}
local_parsed = parse_version(local_ver)
remote_parsed = parse_version(remote_ver)
if remote_parsed <= local_parsed:
return {
"status": "up_to_date",
"local_version": local_ver,
"remote_version": remote_ver,
"message": f"当前已是最新版本: {local_ver}",
}
# 收集 changelog(新接口暂不返回 changelog,预留扩展)
changelog_lines = []
versions = remote_data.get("Response", {}).get("Data", {}).get("Versions", []) if remote_data else []
for v in versions:
v_str = v.get("Version") or v.get("version", "")
v_parsed = parse_version(v_str)
if v_parsed > local_parsed:
desc = v.get("Changelog") or v.get("changelog") or v.get("description") or ""
changelog_lines.append(f" {v_str}: {desc}" if desc else f" {v_str}")
if not changelog_lines and remote_data:
latest_cl = (remote_data.get("Response", {}).get("Data", {}).get("Changelog", "")
or remote_data.get("latestVersion", {}).get("changelog", ""))
if latest_cl:
changelog_lines.append(f" {remote_ver}: {latest_cl}")
return {
"status": "update_available",
"local_version": local_ver,
"remote_version": remote_ver,
"changelog": changelog_lines,
"message": f"发现新版本: {local_ver} → {remote_ver}",
}
# ============== Gateway 连通性检测 ==============
def _tc3_sign(secret_key: str, secret_id: str, host: str, payload_str: str,
action: str, version: str, region: str, timestamp: int) -> dict:
"""生成腾讯云 TC3-HMAC-SHA256 签名,返回请求头字典"""
import hashlib, hmac as _hmac
date = datetime.fromtimestamp(timestamp, tz=timezone.utc).strftime("%Y-%m-%d")
ct = "application/json"
canonical_headers = f"content-type:{ct}\nhost:{host}\nx-tc-action:{action.lower()}\n"
signed_headers = "content-type;host;x-tc-action"
hashed_payload = hashlib.sha256(payload_str.encode("utf-8")).hexdigest()
canonical_request = "\n".join([
"POST", "/", "",
canonical_headers, signed_headers, hashed_payload,
])
algorithm = "TC3-HMAC-SHA256"
credential_scope = f"{date}/{_SERVICE}/tc3_request"
hashed_cr = hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()
string_to_sign = f"{algorithm}\n{timestamp}\n{credential_scope}\n{hashed_cr}"
def _hmac_sha256(key: bytes, msg: str) -> bytes:
return _hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
secret_date = _hmac_sha256(("TC3" + secret_key).encode("utf-8"), date)
secret_service = _hmac_sha256(secret_date, _SERVICE)
secret_signing = _hmac_sha256(secret_service, "tc3_request")
signature = _hmac.new(secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
authorization = (
f"{algorithm} Credential={secret_id}/{credential_scope}, "
f"SignedHeaders={signed_headers}, Signature={signature}"
)
return {
"Host": host, "Content-Type": ct,
"X-TC-Action": action, "X-TC-Version": version,
"X-TC-Timestamp": str(timestamp), "X-TC-Region": region,
"Authorization": authorization,
}
def check_gateway_connectivity(gateway_url: str, secret_key: str,
secret_id: str = "") -> dict:
"""
验证 CMG API 连通性和鉴权状态:发送 SSE 请求,读取前几行 SSE 数据判断结果。
- HTTP 非 200 → 网络/HTTP 错误
- HTTP 200 + SSE 流内含错误事件 → 鉴权失败或业务错误
- HTTP 200 + 收到 delta 或 completed → 连通且鉴权正常
Returns:
{"ok": bool, "code": str, "message": str}
"""
from http.client import HTTPSConnection
import ssl
host = "cmg.ai.tencentcloudapi.com"
region = os.environ.get("CMG_REGION", DEFAULT_REGION)
payload_str = json.dumps({"Input": "ping", "Stream": True}, separators=(",", ":"))
now_ts = int(datetime.now(tz=timezone.utc).timestamp())
headers = _tc3_sign(secret_key, secret_id, host, payload_str,
_ACTION, _VERSION, region, now_ts)
headers["Accept"] = "text/event-stream"
_AUTH_FAIL_CODES = {
"AuthFailure.SecretIdNotFound",
"AuthFailure.SignatureFailure",
"AuthFailure.SignatureExpire",
"AuthFailure.InvalidSecretId",
"AuthFailure.TokenFailure",
"AuthFailure.InvalidAuthorization",
}
try:
ctx = ssl.create_default_context()
conn = HTTPSConnection(host, context=ctx, timeout=20)
conn.request("POST", "/", body=payload_str.encode("utf-8"), headers=headers)
resp = conn.getresponse()
status = resp.status
if status in (401, 403):
conn.close()
return {"ok": False, "code": "AuthError", "message": f"鉴权失败 (HTTP {status}),请检查 AK/SK 是否正确"}
if status != 200:
conn.close()
return {"ok": False, "code": "HTTPError", "message": f"CMG API 返回 HTTP {status}"}
# HTTP 200:先检查 Content-Type
# 鉴权失败时后端返回 text/plain + JSON 错误体(非 SSE 流)
content_type = resp.getheader("Content-Type", "")
if "text/plain" in content_type or "application/json" in content_type:
try:
body = resp.read().decode("utf-8")
data = json.loads(body)
err = data.get("Response", {}).get("Error", {})
err_code = err.get("Code", "")
err_msg = err.get("Message", "")
if err_code in _AUTH_FAIL_CODES:
return {"ok": False, "code": "AuthError", "message": f"鉴权失败: {err_msg}"}
if err_code:
return {"ok": False, "code": err_code, "message": err_msg}
except Exception:
pass
finally:
conn.close()
return {"ok": True, "code": "OK", "message": "CMG API 连通正常"}
# HTTP 200:读取前几行 SSE 数据判断鉴权和业务状态
try:
for _ in range(30): # 最多读 30 行,避免无限等待
raw = resp.readline()
if not raw:
break
line = raw.decode("utf-8").rstrip("\r\n")
if not line or not line.startswith("data:"):
continue
data_str = line[5:].lstrip()
if data_str == "[DONE]":
break
try:
data = json.loads(data_str)
except (json.JSONDecodeError, ValueError):
continue
event_type = data.get("type", "")
# 收到任意进度/增量/完成事件 → 鉴权通过,连通正常
if event_type in (
"run.started", "run.progress",
"message.delta", "message.completed",
# 兼容旧格式
"response.output_text.delta", "response.completed",
):
return {"ok": True, "code": "OK", "message": "CMG API 连通正常"}
# 失败事件 → 提取错误信息
if event_type in ("response.failed", "error"):
err = data.get("response", data).get("error", {})
err_code = err.get("code", "StreamError")
err_msg = err.get("message", str(data))
if err_code in _AUTH_FAIL_CODES or "auth" in err_code.lower():
return {"ok": False, "code": "AuthError", "message": f"鉴权失败: {err_msg}"}
return {"ok": False, "code": "StreamError", "message": err_msg}
# 腾讯云标准错误结构(Response.Error)
if "Response" in data:
err = data["Response"].get("Error", {})
err_code = err.get("Code", "")
err_msg = err.get("Message", "")
if err_code in _AUTH_FAIL_CODES:
return {"ok": False, "code": "AuthError", "message": f"鉴权失败: {err_msg}"}
if err_code:
return {"ok": False, "code": err_code, "message": err_msg}
# 读完了但没有明确成功/失败事件,视为连通(接口已响应)
return {"ok": True, "code": "OK", "message": "CMG API 连通正常"}
finally:
conn.close()
except Exception as e:
return {"ok": False, "code": "NetworkError", "message": f"连接异常: {e}"}
# ============== 主流程 ==============
def main():
# ver_result 在版本检查后用于最终 JSON 摘要
ver_result = None
# ============== 1. 检查 Python 版本 ==============
log_section("1. 检查运行环境")
py_ver = sys.version_info
if py_ver < (3, 7):
log_fail(f"Python 版本过低: {sys.version},需要 Python 3.7+")
sys.exit(1)
log_ok(f"Python {py_ver.major}.{py_ver.minor}.{py_ver.micro} ({platform.system()} {platform.machine()})")
# ============== 2. 检查 Skill 版本更新 ==============
log_section("2. 检查 Skill 版本")
if SKIP_UPDATE:
log_ok("已跳过版本更新检查(--skip-update)")
else:
ver_result = check_version_update()
status = ver_result["status"]
if status == "up_to_date":
log_ok(ver_result["message"])
elif status == "update_available":
log_warn(f"发现新版本 {ver_result['remote_version']}(当前 {ver_result['local_version']}),可前往 SkillHub 更新")
changelog = ver_result.get("changelog", [])
if changelog:
log_info("")
log_info(" === Changelog ===")
for line in changelog:
log_info(line)
log_info("")
elif status in ("check_failed", "no_meta"):
log_warn(ver_result["message"])
log_info(" 版本检查跳过,继续后续检测...")
# ============== 3. 检查 AK/SK 配置 ==============
log_section("3. 检查 AK/SK 配置")
secret_id = os.environ.get("TENCENTCLOUD_SECRET_ID", "")
secret_key = os.environ.get("TENCENTCLOUD_SECRET_KEY", "")
if not secret_id or not secret_key:
missing = []
if not secret_id:
missing.append("TENCENTCLOUD_SECRET_ID")
if not secret_key:
missing.append("TENCENTCLOUD_SECRET_KEY")
log_fail(f"未配置以下环境变量: {', '.join(missing)}")
log_info("")
log_info(" Linux / macOS(写入 ~/.zshrc 或 ~/.bashrc):")
log_info(' echo \'export TENCENTCLOUD_SECRET_ID="your-secret-id"\' >> ~/.zshrc')
log_info(' echo \'export TENCENTCLOUD_SECRET_KEY="your-secret-key"\' >> ~/.zshrc')
log_info(" source ~/.zshrc")
log_info("")
log_info(" Windows PowerShell(写入用户级环境变量):")
log_info(' [Environment]::SetEnvironmentVariable("TENCENTCLOUD_SECRET_ID", "your-secret-id", "User")')
log_info(' [Environment]::SetEnvironmentVariable("TENCENTCLOUD_SECRET_KEY", "your-secret-key", "User")')
log_info("")
log_info(" 密钥获取地址: https://console.cloud.tencent.com/cam/capi")
sys.exit(2)
masked_id = f"{secret_id[:4]}****{secret_id[-4:]}" if len(secret_id) > 8 else "****"
log_ok(f"TENCENTCLOUD_SECRET_ID 已配置: {masked_id}")
log_ok("TENCENTCLOUD_SECRET_KEY 已配置: ****")
# ============== 4. 验证 CMG API 连通性 ==============
log_section("4. 验证 CMG API 连通性")
cmg_host = "cmg.ai.tencentcloudapi.com"
log_ok(f"CMG API: https://{cmg_host}/")
conn_result = check_gateway_connectivity(cmg_host, secret_key, secret_id)
if conn_result["ok"]:
log_ok(conn_result["message"])
else:
code = conn_result["code"]
msg = conn_result["message"]
if code == "AuthError":
log_fail(f"鉴权失败: {msg}")
log_info(" 请检查 TENCENTCLOUD_SECRET_KEY 是否正确")
sys.exit(2)
else:
log_fail(f"Gateway 连通性失败: {msg}")
log_info(f" 请检查网络是否可达 ({cmg_host})")
sys.exit(3)
# ============== 检测完成 ==============
log_info("")
log_info("=== 检测完成 ===")
log_ok("环境就绪,所有功能可用")
log_info("")
log_info(f" [OK] Python {py_ver.major}.{py_ver.minor} ({platform.system()})")
log_info(f" [OK] AK/SK 已配置(SecretId: {masked_id})")
log_info(f" [OK] Gateway 连通正常")
# 输出结构化 JSON 摘要(供 AI 解析),始终打印(不受 --quiet 影响)
summary = {"status": "ready"}
if not SKIP_UPDATE and ver_result and ver_result.get("status") == "update_available":
summary["update_available"] = True
summary["local_version"] = ver_result.get("local_version", "")
summary["remote_version"] = ver_result.get("remote_version", "")
print(json.dumps(summary, ensure_ascii=False))
sys.exit(0)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
MigraQ ChatCompletions SSE 流式调用脚本
通过腾讯云 TC3-HMAC-SHA256 签名调用 CMG ChatCompletions 接口。
接口固定参数:
host: cmg.ai.tencentcloudapi.com
action: ChatCompletions
version: 2024-10-15
region: ap-shanghai(可通过 CMG_REGION 环境变量覆盖)
请求格式:
{"Input": "...", "Stream": true}
响应格式(SSE,text/event-stream):
event: run.started
data: {"type":"run.started","session_id":"..."}
event: message.delta
data: {"type":"message.delta","delta":"..."}
event: message.completed
data: {"type":"message.completed","reply":"...","usage":{...}}
纯 Python 标准库实现,无外部依赖,支持 Windows / Linux / macOS。
用法 (命令行):
python3 migrateq_sse_api.py <question> [session_id]
python3 migrateq_sse_api.py --clear-session
python3 migrateq_sse_api.py --dry-run <question> [session_id]
示例:
python3 migrateq_sse_api.py '阿里云50台ECS如何迁移到腾讯云?'
python3 migrateq_sse_api.py '详细说说 go2tencentcloud 步骤' '550e8400-e29b-41d4-a716-446655440000'
python3 migrateq_sse_api.py --dry-run '测试鉴权是否正确'
作为模块导入:
from migrateq_sse_api import call_sse_api, generate_session_id
session_id = generate_session_id()
result = call_sse_api(question="如何评估迁移成本?", session_id=session_id)
环境变量:
TENCENTCLOUD_SECRET_ID - 腾讯云 SecretId(必填)
TENCENTCLOUD_SECRET_KEY - 腾讯云 SecretKey(必填)
CMG_REGION - 地域(可选,默认 ap-shanghai)
输出格式(统一 JSON):
成功: {"success": true, "action": "ChatCompletions", "data": {"content": "...", "is_final": true, "session_id": "..."}, "requestId": "..."}
失败: {"success": false, "action": "ChatCompletions", "error": {"code": "...", "message": "..."}, "requestId": ""}
"""
import datetime
import hashlib
import hmac
import json
import os
import ssl
import sys
import threading
import time
import uuid
from http.client import HTTPSConnection
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
# ---------------------------------------------------------------------------
# 固定参数
# ---------------------------------------------------------------------------
ACTION = "ChatCompletions"
_HOST = "cmg.ai.tencentcloudapi.com"
_VERSION = "2024-10-15"
_SERVICE = "cmg"
_DEFAULT_REGION = "ap-shanghai"
_AUTH_FAIL_CODES = {
"AuthFailure.SecretIdNotFound",
"AuthFailure.SignatureFailure",
"AuthFailure.SignatureExpire",
"AuthFailure.InvalidSecretId",
"AuthFailure.TokenFailure",
"AuthFailure.InvalidAuthorization",
}
# ---------------------------------------------------------------------------
# 会话管理
# ---------------------------------------------------------------------------
def generate_session_id() -> str:
"""
生成新的 SessionID(UUID v4)。
SessionID 用于控制多轮对话上下文:
- 同一对话的所有轮次必须使用同一个 SessionID
- 用户开启新对话时调用本函数生成新的 SessionID
Returns:
str: UUID v4 格式的 SessionID
"""
return str(uuid.uuid4())
# ---------------------------------------------------------------------------
# 内部工具函数
# ---------------------------------------------------------------------------
def _get_ssl_context():
"""获取 SSL 上下文,始终启用证书验证"""
try:
import certifi
return ssl.create_default_context(cafile=certifi.where())
except ImportError:
return ssl.create_default_context()
def _make_error(code: str, message: str, request_id: str = "") -> dict:
"""构造统一错误结果"""
return {
"success": False,
"action": ACTION,
"error": {"code": code, "message": message},
"requestId": request_id,
}
def _make_success(data: dict, request_id: str = "") -> dict:
"""构造统一成功结果"""
return {
"success": True,
"action": ACTION,
"data": data,
"requestId": request_id,
}
def _resolve_credentials(secret_id: str = None, secret_key: str = None):
"""读取 AK/SK 凭证(优先参数,其次环境变量)"""
secret_id = secret_id or os.environ.get("TENCENTCLOUD_SECRET_ID", "")
secret_key = secret_key or os.environ.get("TENCENTCLOUD_SECRET_KEY", "")
return secret_id, secret_key
def _check_credentials(secret_id: str, secret_key: str):
"""
检查 AK/SK 是否已配置,未配置则返回错误结果。
Returns:
None 表示凭证完整;dict 表示缺失,包含引导信息。
"""
missing = []
if not secret_id:
missing.append("TENCENTCLOUD_SECRET_ID")
if not secret_key:
missing.append("TENCENTCLOUD_SECRET_KEY")
if not missing:
return None
guide = (
"请先配置腾讯云 API 密钥后再使用 MigraQ。\n"
f" 缺少环境变量: {', '.join(missing)}\n"
"\n"
" Linux / macOS(写入 ~/.zshrc 或 ~/.bashrc):\n"
' echo \'export TENCENTCLOUD_SECRET_ID="your-secret-id"\' >> ~/.zshrc\n'
' echo \'export TENCENTCLOUD_SECRET_KEY="your-secret-key"\' >> ~/.zshrc\n'
" source ~/.zshrc\n"
"\n"
" Windows PowerShell(写入用户级环境变量):\n"
' [Environment]::SetEnvironmentVariable("TENCENTCLOUD_SECRET_ID", "your-secret-id", "User")\n'
' [Environment]::SetEnvironmentVariable("TENCENTCLOUD_SECRET_KEY", "your-secret-key", "User")\n'
"\n"
" 密钥获取地址: https://console.cloud.tencent.com/cam/capi"
)
return _make_error("MissingCredentials", guide)
def _tc3_sign(secret_key: str, secret_id: str, host: str, payload_str: str,
action: str, version: str, region: str, timestamp: int) -> dict:
"""
生成腾讯云 TC3-HMAC-SHA256 签名,返回请求头字典。
参考: https://cloud.tencent.com/document/api/213/30654
"""
date = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc).strftime("%Y-%m-%d")
# Step 1: 构造规范请求(SignedHeaders 包含 x-tc-action,提升签名安全性)
ct = "application/json"
canonical_headers = f"content-type:{ct}\nhost:{host}\nx-tc-action:{action.lower()}\n"
signed_headers = "content-type;host;x-tc-action"
hashed_payload = hashlib.sha256(payload_str.encode("utf-8")).hexdigest()
canonical_request = "\n".join([
"POST", "/", "",
canonical_headers, signed_headers, hashed_payload,
])
# Step 2: 构造待签字符串
algorithm = "TC3-HMAC-SHA256"
credential_scope = f"{date}/{_SERVICE}/tc3_request"
hashed_cr = hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()
string_to_sign = f"{algorithm}\n{timestamp}\n{credential_scope}\n{hashed_cr}"
# Step 3: 计算签名
def _hmac_sha256(key: bytes, msg: str) -> bytes:
return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
secret_date = _hmac_sha256(("TC3" + secret_key).encode("utf-8"), date)
secret_service = _hmac_sha256(secret_date, _SERVICE)
secret_signing = _hmac_sha256(secret_service, "tc3_request")
signature = hmac.new(secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
# Step 4: 构造 Authorization
authorization = (
f"{algorithm} Credential={secret_id}/{credential_scope}, "
f"SignedHeaders={signed_headers}, Signature={signature}"
)
return {
"Host": host,
"Content-Type": ct,
"X-TC-Action": action,
"X-TC-Version": version,
"X-TC-Timestamp": str(timestamp),
"X-TC-Region": region,
"X-TC-Language": "zh-CN",
"Authorization": authorization,
}
# ---------------------------------------------------------------------------
# SSE 行解析
# ---------------------------------------------------------------------------
def parse_sse_line(line: str):
"""
解析单行 SSE 数据。
Returns:
dict | None:
- id 行: {"type": "id", "value": "..."}
- event 行: {"event": "<value>"}
- data 行(JSON 有效): {"event": "data", "data": {...}}
- data 行(JSON 无效): {"event": "data", "raw": "..."}
- 空行/注释行: None
"""
if not line or line.startswith(":"):
return None
if line.startswith("id:"):
return {"type": "id", "value": line[3:].strip()}
if line.startswith("data:"):
payload = line[5:].lstrip()
try:
return {"event": "data", "data": json.loads(payload)}
except (json.JSONDecodeError, ValueError):
return {"event": "data", "raw": payload}
if line.startswith("event:"):
return {"event": line[6:].strip()}
return None
# ---------------------------------------------------------------------------
# SSE 流式 API 调用
# ---------------------------------------------------------------------------
def call_sse_api(question: str, session_id: str = None,
region: str = None,
secret_id: str = None, secret_key: str = None,
on_delta=None, timeout: int = 600) -> dict:
"""
调用 ChatCompletions SSE 流式 API。
Args:
question: 用户问题(必填)
session_id: 会话 ID(必传,首次调用不传则自动生成 UUID v4)。
作为 SessionKey 传入服务端,服务端按此字段隔离对话上下文。
多轮对话时必须传入上次返回的 session_id 以保持上下文。
region: 地域,不传则从 CMG_REGION 环境变量读取,默认 ap-shanghai
secret_id: 腾讯云 SecretId,不传则从环境变量读取
secret_key: 腾讯云 SecretKey,不传则从环境变量读取
on_delta: 回调函数,每收到一段流式文本时调用,参数为 str
timeout: 请求超时秒数,默认 600
Returns:
dict: 统一格式的结果字典
"""
region = region or os.environ.get("CMG_REGION", _DEFAULT_REGION)
secret_id, secret_key = _resolve_credentials(secret_id, secret_key)
cred_err = _check_credentials(secret_id, secret_key)
if cred_err:
return cred_err
# 首次调用未传入 session_id 时自动生成,多轮对话时沿用上次返回的值
if not session_id:
session_id = generate_session_id()
payload = {"Input": question, "Stream": True, "SessionKey": session_id}
payload_str = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
now = datetime.datetime.now(datetime.timezone.utc)
timestamp = int(now.timestamp())
headers = _tc3_sign(secret_key, secret_id, _HOST, payload_str,
ACTION, _VERSION, region, timestamp)
headers["Accept"] = "text/event-stream"
# 使用 HTTPSConnection 实现无缓冲实时 SSE 读取
try:
ctx = _get_ssl_context()
conn = HTTPSConnection(_HOST, context=ctx, timeout=timeout)
conn.request("POST", "/", body=payload_str.encode("utf-8"), headers=headers)
resp = conn.getresponse()
except Exception as e:
return _make_error("NetworkError", f"无法连接 CMG API ({_HOST}): {e}")
if resp.status != 200:
try:
body = resp.read().decode("utf-8")
data = json.loads(body)
err = data.get("Response", {}).get("Error", {})
code = err.get("Code", "")
msg = err.get("Message") or f"HTTP {resp.status}"
except Exception:
code = ""
msg = f"HTTP {resp.status}"
conn.close()
# 鉴权失败(401/403 或 AuthFailure 错误码)单独返回,方便上层区分处理
if resp.status in (401, 403) or code in _AUTH_FAIL_CODES:
return _make_error("AuthError", f"鉴权失败,请检查 AK/SK 是否正确: {msg}")
return _make_error("HTTPError", f"CMG API 返回错误: {msg}")
# HTTP 200:检查 Content-Type
# 鉴权失败时后端返回 text/plain + JSON 错误体,不是 SSE 流
content_type = resp.getheader("Content-Type", "")
if "text/plain" in content_type or "application/json" in content_type:
try:
body = resp.read().decode("utf-8")
data = json.loads(body)
err = data.get("Response", {}).get("Error", {})
code = err.get("Code", "")
msg = err.get("Message", body)
request_id = data.get("Response", {}).get("RequestId", "")
if code in _AUTH_FAIL_CODES or code.startswith("AuthFailure"):
return _make_error("AuthError", f"鉴权失败,请检查 AK/SK 是否正确: {msg}", request_id)
if code:
return _make_error("HTTPError", f"CMG API 返回错误 [{code}]: {msg}", request_id)
return _make_error("StreamError", "CMG API 返回非流式响应且无法解析")
except Exception as e:
return _make_error("StreamError", f"CMG API 返回非流式响应: {e}")
finally:
conn.close()
try:
return _parse_sse_stream(resp, session_id, on_delta)
finally:
conn.close()
def clear_session(region: str = None,
secret_id: str = None, secret_key: str = None) -> dict:
"""
清除当前会话上下文(本接口无状态,调用方重新生成 session_id 即可)。
为保持接口兼容性而保留,实际执行为空操作并返回成功。
Returns:
dict: 统一格式的结果字典
"""
return _make_success({"message": "session cleared"})
def _parse_sse_stream(resp, session_id: str, on_delta) -> dict:
"""
解析 ChatCompletions SSE 流并构建结果。
实际响应格式:
event: run.started
data: {"type":"run.started","session_id":"..."}
event: run.progress
data: {"type":"run.progress","stage":"...","summary":"..."}
event: message.delta
data: {"type":"message.delta","delta":"..."}
event: message.completed
data: {"type":"message.completed","reply":"...","usage":{...}}
"""
content_parts = []
request_id = ""
usage = {}
first_delta_received = False
stream_error = None # 记录 SSE 流内的业务错误
# 心跳线程:在收到第一个文本增量前,每 10 秒向 stderr 输出一次等待提示
stop_heartbeat = threading.Event()
def _heartbeat():
elapsed = 0
while not stop_heartbeat.wait(10):
if first_delta_received:
break
elapsed += 10
print(f"[MigraQ] 远端专家处理中,已等待 {elapsed} 秒,请勿中断……", file=sys.stderr, flush=True)
heartbeat_thread = threading.Thread(target=_heartbeat, daemon=True)
heartbeat_thread.start()
try:
# 使用 readline() 逐行读取,无缓冲,保证 SSE 实时性
while True:
raw = resp.readline()
if not raw:
break
line = raw.decode("utf-8").rstrip("\r\n")
if line == "" or line.startswith(":"):
continue
if line.startswith("event:"):
continue # event 类型已通过 data.type 区分,无需单独处理
if not line.startswith("data:"):
continue
data_str = line[5:].lstrip()
if data_str == "[DONE]":
break
try:
data = json.loads(data_str)
except (json.JSONDecodeError, ValueError):
continue
event_type = data.get("type", "")
# 流式文本增量
if event_type == "message.delta":
delta = data.get("delta", "")
if delta:
if not first_delta_received:
first_delta_received = True
stop_heartbeat.set()
content_parts.append(delta)
if on_delta:
on_delta(delta)
# 完成事件:提取完整回复和 usage
elif event_type == "message.completed":
# 若流式 delta 已拼接内容则保留,否则用 reply 字段兜底
if not content_parts:
reply = data.get("reply", "")
if reply:
content_parts.append(reply)
if on_delta:
on_delta(reply)
usage = data.get("usage", {})
break
# 失败事件:后端通过 SSE 流返回的业务错误
elif event_type in ("response.failed", "error"):
err_obj = data.get("response", data)
err_detail = err_obj.get("error", {})
err_code = err_detail.get("code") or err_obj.get("code", "StreamError")
err_msg = err_detail.get("message") or err_obj.get("message", str(data))
stream_error = _make_error("StreamError", f"远端服务返回错误 [{err_code}]: {err_msg}", request_id)
break
# 兼容:data 中直接含 Response.Error 字段(部分腾讯云接口格式)
elif "Response" in data:
resp_err = data["Response"].get("Error")
if resp_err:
err_code = resp_err.get("Code", "StreamError")
err_msg = resp_err.get("Message", str(resp_err))
request_id = data["Response"].get("RequestId", "")
stream_error = _make_error("StreamError", f"远端服务返回错误 [{err_code}]: {err_msg}", request_id)
break
finally:
stop_heartbeat.set()
# 优先返回流内业务错误
if stream_error:
return stream_error
# SSE 流结束但无任何内容且无错误事件,视为异常
if not content_parts:
return _make_error("StreamError", "远端服务未返回任何内容,请稍后重试或检查网络连接", request_id)
return _make_success(
{
"content": "".join(content_parts),
"is_final": True,
"session_id": session_id,
"usage": usage,
},
request_id,
)
# ---------------------------------------------------------------------------
# 命令行入口
# ---------------------------------------------------------------------------
def _output_json(obj: dict) -> str:
return json.dumps(obj, ensure_ascii=False)
def main():
"""命令行入口:python3 migrateq_sse_api.py <question> [session_id]"""
args = sys.argv[1:]
# --clear-session 模式
if args and args[0] == "--clear-session":
result = clear_session()
print(_output_json(result))
sys.exit(0 if result.get("success") else 1)
# --dry-run 模式:仅打印签名请求头和 payload,不发送请求
dry_run = False
if args and args[0] == "--dry-run":
dry_run = True
args = args[1:]
if len(args) < 1:
print(_output_json(_make_error(
"MissingParameter",
"用法: python3 migrateq_sse_api.py <question> [session_id]\n"
" python3 migrateq_sse_api.py --dry-run <question> [session_id]\n"
" python3 migrateq_sse_api.py --clear-session"
)))
sys.exit(1)
question = args[0]
session_id = args[1] if len(args) > 1 else None # 首次调用由 call_sse_api 内部生成
if dry_run:
region, secret_id, secret_key = (
os.environ.get("CMG_REGION", _DEFAULT_REGION),
*_resolve_credentials(),
)
cred_err = _check_credentials(secret_id, secret_key)
if cred_err:
print(_output_json(cred_err))
sys.exit(1)
if not session_id:
session_id = generate_session_id()
payload = {"Input": question, "Stream": True, "SessionKey": session_id}
payload_str = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
timestamp = int(datetime.datetime.now(datetime.timezone.utc).timestamp())
headers = _tc3_sign(secret_key, secret_id, _HOST, payload_str,
ACTION, _VERSION, region, timestamp)
headers["Accept"] = "text/event-stream"
dry_run_info = {
"success": True,
"action": "DryRun",
"data": {
"endpoint": f"https://{_HOST}",
"session_id": session_id,
"payload": payload,
"headers": headers,
},
}
print(_output_json(dry_run_info))
sys.exit(0)
def on_delta(delta: str):
print(delta, end="", flush=True)
result = call_sse_api(question=question, session_id=session_id, on_delta=on_delta)
# 流式输出结束后换行,再打印统一 JSON 结果
print()
print(_output_json(result))
if not result.get("success"):
sys.exit(1)
if __name__ == "__main__":
main()
Related skills
FAQ
Which source clouds does it scan?
It scans AWS, Alibaba Cloud, Huawei Cloud, and GCP resources.
How does it authenticate?
It uses Tencent Cloud AK/SK credentials with TC3-HMAC-SHA256 signing to call the CMG ChatCompletions API.
What credentials are required?
The TENCENTCLOUD_SECRET_ID and TENCENTCLOUD_SECRET_KEY environment variables.