第25章:CrewAI 与消息系统集成——Slack、飞书、企业微信机器人
1. 项目背景
某互联网公司的运维团队每天要处理 10+ 个线上故障——从数据库慢查询到微服务雪崩。值班人员收到告警后,需要快速判断故障原因、查找历史类似事件、给出排查步骤。但凌晨 3 点的值班人员经验不一定够——有时候一个简单问题排查了半小时,而有经验的同事在睡梦中。
运维 Leader 想做一个"研发值班助手"机器人——接入团队的飞书群聊。任何人 @机器人 描述故障现象,机器人自动调用 CrewAI 分析故障、查询日志、检索历史相似事件、给出排查步骤和升级建议。关键设计:
- 群聊里 @机器人 触发,不是 1v1 私聊
- 机器人先回复"正在分析…",避免用户干等
- 1-2 分钟内给出排查结果(不能太久,否则用户已经自己搜到了)
- 防止刷屏——同一故障 5 分钟内多次问到只回答一次
本章的目标是实现一个消息系统集成层——让 CrewAI Agent 进入团队的日常协作工具,成为"群里的一员"。
2. 项目设计
小胖(刷着手机):“大师,消息机器人不就是 Webhook 收到消息 → 调用 CrewAI → 回复消息吗?”
大师:“表面上是。但聊天场景有三个和 API 不同的挑战。第一,噪声控制——群聊里 90% 的消息不是问机器人的,你怎么判断哪条消息是给你的?第二,上下文识别——用户说’还是不行’——你得上翻聊天记录才知道他在说哪个故障。第三,体验设计——AI 分析了 2 分钟,用户等了 2 分钟,你在这期间是静默还是给个进度提示?”
小白(翻开笔记本):“消息路由是什么意思?”
大师:“你在群里 @机器人,消息到了 Webhook。Webhook 需要判断:这是要 AI 分析故障,还是只是打个招呼,还是命令机器人’查看今天的告警统计’。这就是指令解析——通常用前缀匹配或一个轻量 LLM 来做意图识别。不只是故障排查——将来可能扩展成’发布确认’‘查看监控’'查询文档’等多个能力。”
小胖:“异步回复怎么做?比如机器人先回’收到,正在分析’,1分钟后分析完了再发一条详细结果。”
大师:“Webhook 先快速返回一个 200(确认收到),然后异步处理。处理过程中:第一步,发一条’收到,正在分析故障信息…‘作为即时反馈;第二步,如果分析超过 30 秒,发一条进度更新’正在查询相关日志和历史事件…’;第三步,分析完成后发最终结果。这需要消息平台支持’主动发消息’(通常是 Bot API 而非 Webhook 响应)。”
小白:“防刷策略怎么设计?同一个问题反复问怎么办?”
大师:“用去重键——取最近 5 分钟内相同用户相同故障描述的哈希。如果 5 分钟内有人问过,直接回复’该故障正在分析中(5分钟前已触发),已有结果请上翻查看:xxx’。但跨用户的相同故障应该单独回答——因为不同人可能有不同角度的问题。去重键按 user_id + content_hash 生成,这样既避免重复,又不影响不同用户。”
技术映射总结:消息机器人 = Webhook 接收(鉴权+去重)→ 指令解析(前缀/LLM 意图识别)→ 异步 Crew 执行 → 进度推送 → 结果回复。关键设计原则:快速响应(200ms 内返回 200)、异步处理、进度可见、防刷防噪。
3. 项目实战
3.1 实战目标
实现一个"研发值班助手"机器人——在群聊中 @机器人 描述故障,自动调用 CrewAI 生成排查步骤和升级建议。
3.2 环境准备
devops-bot/
├── .env
├── main.py
├── bot/
│ ├── __init__.py
│ ├── bot_server.py # Webhook 接收(FastAPI)
│ ├── message_router.py # 消息路由和指令解析
│ ├── context_builder.py # 上下文构建(翻聊天记录)
│ └── dedup.py # 去重防刷
├── crew/
│ ├── agents/
│ │ ├── incident_analyzer.py
│ │ └── historian.py
│ ├── tasks/
│ │ └── incident_task.py
│ └── incident_crew.py
├── outputs/
└── requirements.txt
依赖:crewai、fastapi、uvicorn、python-dotenv
3.3 分步实现
步骤1:去重防刷模块
目标:避免同一故障在短时间内被重复分析。
# bot/dedup.py
import hashlib
import time
import threading
from typing import Optional
class DedupManager:
"""消息去重管理器——防止短时间重复分析同一故障"""
def __init__(self, window_seconds: int = 300):
self.window = window_seconds # 5 分钟窗口
self._records: dict = {} # key -> timestamp
self._lock = threading.Lock()
def _generate_key(self, user_id: str, content: str) -> str:
"""生成去重键"""
raw = f"{user_id}:{content[:100]}"
return hashlib.md5(raw.encode()).hexdigest()
def is_duplicate(self, user_id: str, content: str) -> tuple:
"""
检查是否重复。
返回 (is_dup: bool, original_time: Optional[str])
"""
key = self._generate_key(user_id, content)
now = time.time()
with self._lock:
# 清理过期记录
self._records = {
k: t for k, t in self._records.items()
if now - t < self.window
}
if key in self._records:
original_time = time.strftime(
"%H:%M:%S", time.localtime(self._records[key])
)
return (True, original_time)
self._records[key] = now
return (False, None)
# 全局去重实例
dedup = DedupManager(window_seconds=300)
步骤2:上下文构建——翻聊天记录
目标:从消息历史中获取故障的完整上下文。
# bot/context_builder.py
from typing import List, Dict
class ContextBuilder:
"""消息上下文构建器——从聊天记录中提取故障背景"""
@staticmethod
def extract_incident_context(
current_message: str,
recent_messages: List[Dict] = None
) -> str:
"""
从当前消息和最近消息中构建故障上下文。
recent_messages: 最近 N 条消息(按时间倒序),每条格式:
{"user": "张三", "content": "支付接口超时了", "timestamp": "..."}
"""
parts = [f"【当前故障报告】\n{current_message}"]
if recent_messages:
# 提取可能相关的上下文(包含关键错误词的)
error_keywords = [
"错误", "error", "超时", "timeout", "500", "502", "503",
"异常", "exception", "失败", "fail", "崩溃", "crash",
"告警", "alert", "宕机", "down"
]
relevant = []
for msg in recent_messages[:20]: # 只看最近 20 条
content = msg.get("content", "")
if any(kw in content.lower() for kw in error_keywords):
relevant.append(
f"- [{msg.get('timestamp', '?')}] {msg.get('user', '?')}: {content[:100]}"
)
if relevant:
parts.append(f"\n【相关历史消息】\n" + "\n".join(relevant))
return "\n".join(parts)
@staticmethod
def extract_user_role(user_id: str) -> str:
"""从用户 ID 推断角色——生产环境对接 LDAP/HR 系统"""
role_map = {
"ops_": "运维工程师",
"dev_": "后端开发",
"qa_": "测试工程师",
"pm_": "产品经理",
"default": "团队成员"
}
for prefix, role in role_map.items():
if user_id.startswith(prefix):
return role
return role_map["default"]
步骤3:消息路由器——指令解析
目标:判断消息意图,路由到对应的处理流程。
# bot/message_router.py
import re
from typing import Optional, Tuple
from enum import Enum
class Intent(str, Enum):
INCIDENT_ANALYSIS = "incident_analysis" # 故障分析
STATUS_QUERY = "status_query" # 状态查询
HELP = "help" # 帮助
UNKNOWN = "unknown" # 无法识别
class MessageRouter:
"""消息路由器——解析用户意图"""
# 意图识别规则
INCIDENT_PATTERNS = [
r'(故障|报错|异常|崩了|宕机|超时|500|502|503|4\d{2}|5\d{2})',
r'(出问题了|挂了|不行了|不能用了|访问不了)',
r'(排查|定位|分析|诊断).*(问题|原因|故障)',
r'(帮忙|帮我).*(看|查|分析|定位)',
]
STATUS_PATTERNS = [
r'(状态|情况|进展).*(怎样|如何|怎么样)',
r'(查|看).*(告警|监控|日志)',
]
HELP_PATTERNS = [
r'(帮助|help|怎么用|能做什么|功能)',
]
@classmethod
def classify(cls, message: str) -> Intent:
"""分类用户消息的意图"""
text = message.lower().strip()
# 检查故障分析意图
for pattern in cls.INCIDENT_PATTERNS:
if re.search(pattern, text):
return Intent.INCIDENT_ANALYSIS
# 检查状态查询意图
for pattern in cls.STATUS_PATTERNS:
if re.search(pattern, text):
return Intent.STATUS_QUERY
# 检查帮助意图
for pattern in cls.HELP_PATTERNS:
if re.search(pattern, text):
return Intent.HELP
return Intent.UNKNOWN
@classmethod
def extract_keywords(cls, message: str) -> str:
"""从消息中提取关键排查信息"""
# 移除 @提及
text = re.sub(r'<@[^>]+>', '', message).strip()
# 提取服务名、接口名等
services = re.findall(
r'(支付|订单|用户|认证|消息|推送|搜索|推荐|网关|API)\S*(服务|接口|系统|模块)',
text
)
if services:
return f"涉及服务: {', '.join([''.join(s) for s in services])}"
return text[:200]
步骤4:故障分析 Crew
目标:分析故障、给出排查步骤。
# crew/agents/incident_analyzer.py
from crewai import Agent
def create_incident_analyzer(llm):
return Agent(
role="故障排查工程师",
goal=(
"分析故障现象,快速定位可能原因,给出结构化的排查步骤。"
"每步排查要给出来源依据(日志关键字、监控指标、历史事件)。"
),
backstory=(
"你是 SRE 团队的资深工程师,参与过 500+ 次线上故障应急。"
"你的排查逻辑:先看现象(什么服务、什么错误、什么时间),"
"再按可能性排序(最近变更 > 资源耗尽 > 依赖故障 > 代码 Bug),"
"每个排查方向给出具体的检查命令或指标。"
"你追求实用——'"查看日志'太模糊,要说'tail -f /var/log/app.log | grep ERROR'"。"
),
llm=llm,
verbose=True,
allow_delegation=False,
max_iter=10
)
def create_historian(llm):
return Agent(
role="故障历史研究员",
goal="从历史故障记录中查找相似事件,提供参考解决方案。",
backstory=(
"你管理着公司所有线上故障的 Postmortem 文档库。"
"当新故障出现时,你迅速匹配相似历史事件——"
"相同的错误信息、相同的服务、相同的时间模式。"
"你输出格式:相似事件摘要 + 当时的解决方案 + 是否适用当前情况。"
),
llm=llm,
verbose=True,
allow_delegation=False,
max_iter=8
)
# crew/incident_crew.py
from crewai import Crew, Process, Task
from crew.agents.incident_analyzer import create_incident_analyzer, create_historian
import os
def run_incident_analysis(incident_description: str, llm) -> dict:
"""运行故障分析 Crew"""
analyzer = create_incident_analyzer(llm)
historian = create_historian(llm)
analysis_task = Task(
description=f"""
请分析以下故障现象,给出排查步骤:
【故障描述】
{incident_description}
【分析要求】
1. 初步判断:最可能的 2-3 个根因方向(按可能性排序)
2. 排查步骤(每步包含:检查命令/指标 + 预期结果 + 异常处理)
3. 影响范围评估
4. 升级建议(什么情况下需要升级给谁)
""",
expected_output=(
"# 故障分析报告\n"
"## 初步判断\n"
"## 排查步骤\n"
"### 步骤1: ...\n"
"- 检查方法: ...\n"
"- 预期结果: ...\n"
"- 如果不是预期: ...\n\n"
"## 影响范围\n"
"## 升级建议"
),
agent=analyzer
)
history_task = Task(
description=f"检索与以下故障相似的历史事件:{incident_description[:500]}",
expected_output=(
"# 相似历史事件\n"
"| 事件 | 时间 | 根因 | 解决方案 | 是否适用 |\n"
"|------|------|------|---------|--------|"
),
agent=historian
)
crew = Crew(
agents=[analyzer, historian],
tasks=[analysis_task, history_task],
process=Process.sequential,
verbose=False
)
result = crew.kickoff()
return {
"success": True,
"analysis": result.raw,
"length": len(result.raw) if result.raw else 0
}
步骤5:机器人 Webhook 服务器
目标:接收消息、路由处理、异步回复。
# bot/bot_server.py
import json
import os
import time
import threading
from datetime import datetime
from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
from fastapi.responses import JSONResponse
from bot.message_router import MessageRouter, Intent
from bot.context_builder import ContextBuilder
from bot.dedup import dedup
bot_app = FastAPI(title="研发值班助手 Bot")
# 模拟的消息回复函数——生产环境替换为飞书/Slack API 调用
def send_message_to_channel(channel_id: str, content: str, thread_id: str = None):
"""发送消息到群聊——生产环境调用飞书/Slack API"""
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"\n📤 [{timestamp}] 发送到 {channel_id}:")
print(f" {content[:200]}...")
# 实际 API 调用:
# requests.post("https://open.feishu.cn/open-apis/im/v1/messages/...", ...)
@bot_app.post("/webhook")
async def handle_message(request: Request, background_tasks: BackgroundTasks):
"""
接收群聊消息 Webhook。
支持的平台:飞书、Slack、企业微信(消息格式统一处理)
"""
try:
body = await request.json()
except:
raise HTTPException(status_code=400, detail="无效的 JSON 格式")
# 解析消息(适配飞书格式——其他平台类似调整)
event = body.get("event", body)
message = event.get("message", event)
sender = event.get("sender", {})
sender_id = sender.get("sender_id", body.get("user_id", "unknown"))
content = message.get("content", "")
if isinstance(content, str):
try:
content_obj = json.loads(content)
content = content_obj.get("text", content)
except:
pass
channel_id = event.get("chat_id", body.get("channel_id", "unknown"))
timestamp = event.get("timestamp", str(int(time.time())))
print(f"\n📥 [{datetime.now().strftime('%H:%M:%S')}] {sender_id}: {content[:100]}")
# 1. 去重检查
is_dup, dup_time = dedup.is_duplicate(sender_id, content)
if is_dup:
send_message_to_channel(
channel_id,
f"🔄 该故障 {dup_time} 已触发分析,请查看之前的分析结果。"
f"如有新发现请补充描述。"
)
return JSONResponse({"status": "duplicate"})
# 2. 意图分类
intent = MessageRouter.classify(content)
# 3. 按意图处理
if intent == Intent.INCIDENT_ANALYSIS:
# 立即回复确认
send_message_to_channel(channel_id, "🔍 收到故障报告,正在分析中...")
# 异步执行分析
context = ContextBuilder.extract_incident_context(content)
keywords = MessageRouter.extract_keywords(content)
background_tasks.add_task(
run_analysis_background,
channel_id=channel_id,
incident_description=f"{context}\n\n{keywords}",
sender_id=sender_id
)
return JSONResponse({"status": "analyzing", "intent": intent})
elif intent == Intent.STATUS_QUERY:
send_message_to_channel(channel_id, "📊 状态查询功能开发中,即将上线。当前支持:故障分析。")
return JSONResponse({"status": "unsupported", "intent": intent})
elif intent == Intent.HELP:
help_text = (
"🤖 **研发值班助手** 可以帮你:\n"
"• 故障分析:@我 描述故障现象,自动生成排查步骤\n"
"• 示例:@助手 支付接口返回 503,日志显示连接池耗尽\n"
"• 查询命令:@助手 help 获取帮助"
)
send_message_to_channel(channel_id, help_text)
return JSONResponse({"status": "help", "intent": intent})
else:
# 未识别意图——不回复(避免噪音)
return JSONResponse({"status": "ignored", "intent": intent})
def run_analysis_background(channel_id: str, incident_description: str, sender_id: str):
"""后台执行故障分析——在独立线程中运行"""
from crewai import LLM
from crew.incident_crew import run_incident_analysis
try:
# 发送进度更新
send_message_to_channel(
channel_id,
"⏳ 正在检索历史相似事件和系统日志..."
)
llm = LLM(
model=os.getenv("MODEL_NAME", "gpt-4o-mini"),
temperature=0.3, timeout=120
)
result = run_incident_analysis(incident_description, llm)
if result["success"]:
# 格式化分析结果为群聊友好的格式
analysis_text = result["analysis"]
# 截断过长结果
if len(analysis_text) > 3000:
analysis_text = analysis_text[:3000] + "\n\n...(完整报告请联系值班人员)"
send_message_to_channel(
channel_id,
f"✅ **故障分析完成**\n\n{analysis_text}\n\n"
f"---\n💡 提示:如排查后仍无法解决,请升级给 @SRE团队"
)
else:
send_message_to_channel(
channel_id,
"❌ 分析过程出现异常,建议人工排查。"
)
except Exception as e:
send_message_to_channel(
channel_id,
f"❌ 分析失败:{type(e).__name__}。请稍后重试或联系人工值班。"
)
步骤6:主入口
# main.py
from dotenv import load_dotenv
load_dotenv()
import uvicorn
from bot.bot_server import bot_app
import os
if __name__ == "__main__":
os.makedirs("outputs", exist_ok=True)
print("=" * 60)
print("🤖 研发值班助手 Bot 启动中...")
print(f" Webhook: http://0.0.0.0:8000/webhook")
print(f" 模型: {os.getenv('MODEL_NAME', 'gpt-4o-mini')}")
print("=" * 60)
uvicorn.run(bot_app, host="0.0.0.0", port=8000)
3.4 运行结果
# 启动 Bot
python main.py
# 🤖 研发值班助手 Bot 启动中...
# Webhook: http://0.0.0.0:8000/webhook
# --- 模拟群聊消息:用 curl 测试 Webhook ---
# 测试1: 故障分析
curl -X POST http://localhost:8000/webhook \
-H "Content-Type: application/json" \
-d '{"event":{"sender":{"sender_id":"ops_zhang"},"message":{"content":"支付接口大量返回503,看日志显示connection pool timeout"},"chat_id":"ops_group"}}'
# 返回: {"status":"analyzing","intent":"incident_analysis"}
# 机器人发送:
# 🔍 收到故障报告,正在分析...
# ⏳ 正在检索历史相似事件和系统日志...
# ✅ 故障分析完成
# 测试2: 5分钟内再次问相同问题——被去重拦截
curl -X POST http://localhost:8000/webhook \
-d '{"event":{"sender":{"sender_id":"ops_zhang"},"message":{"content":"支付接口大量返回503..."},"chat_id":"ops_group"}}'
# 返回: {"status":"duplicate"}
# 机器人发送:
# 🔄 该故障 15:30:22 已触发分析,请查看之前的分析结果。
# 测试3: 打招呼——不回复
curl -X POST http://localhost:8000/webhook \
-d '{"event":{"sender":{"sender_id":"user1"},"message":{"content":"早上好"},"chat_id":"ops_group"}}'
# 返回: {"status":"ignored","intent":"unknown"}
# (无回复——避免噪音)
3.5 测试验证
# test_bot.py
import pytest
import time
from bot.message_router import MessageRouter, Intent
from bot.dedup import DedupManager
from bot.context_builder import ContextBuilder
def test_classify_incident():
"""验证故障报告被正确识别"""
assert MessageRouter.classify("支付接口500错误") == Intent.INCIDENT_ANALYSIS
assert MessageRouter.classify("数据库崩了") == Intent.INCIDENT_ANALYSIS
assert MessageRouter.classify("帮忙排查一下用户登录失败") == Intent.INCIDENT_ANALYSIS
def test_classify_status():
"""验证状态查询被正确识别"""
assert MessageRouter.classify("系统现在状态怎么样") == Intent.STATUS_QUERY
assert MessageRouter.classify("查一下最近告警") == Intent.STATUS_QUERY
def test_classify_unknown():
"""验证无关消息不触发处理"""
assert MessageRouter.classify("今天天气真好") == Intent.UNKNOWN
assert MessageRouter.classify("有人一起吃饭吗") == Intent.UNKNOWN
def test_dedup_first_time():
"""验证首次消息不重复"""
mgr = DedupManager(window_seconds=10)
is_dup, _ = mgr.is_duplicate("user1", "支付接口500错误")
assert is_dup == False
def test_dedup_duplicate():
"""验证重复消息被拦截"""
mgr = DedupManager(window_seconds=10)
mgr.is_duplicate("user1", "支付接口500错误")
is_dup, _ = mgr.is_duplicate("user1", "支付接口500错误")
assert is_dup == True
def test_dedup_different_user():
"""验证不同用户的相同消息不被拦截"""
mgr = DedupManager(window_seconds=10)
mgr.is_duplicate("user1", "支付接口500错误")
is_dup, _ = mgr.is_duplicate("user2", "支付接口500错误")
assert is_dup == False
def test_context_extraction():
"""验证故障上下文提取"""
recent = [
{"user": "张三", "content": "接口响应很慢", "timestamp": "10:01"},
{"user": "李四", "content": "看到了 timeout", "timestamp": "10:02"},
{"user": "王五", "content": "中午吃什么", "timestamp": "10:03"}, # 无关消息
]
context = ContextBuilder.extract_incident_context("支付 503", recent)
assert "timeout" in context
assert "中午吃什么" not in context # 无关消息被过滤
def test_keyword_extraction():
"""验证关键词提取"""
msg = "@助手 支付服务接口返回503,日志显示Redis连接超时"
keywords = MessageRouter.extract_keywords(msg)
assert "支付服务" in keywords
pytest test_bot.py -v
# 8 passed
4. 项目总结
4.1 优点与缺点
| 维度 | 消息机器人 | Web API(第24章) | CLI |
|---|---|---|---|
| 使用门槛 | 极低(聊天即用) | 中(需 HTTP 客户端) | 高(需终端) |
| 响应延迟 | 1-2分钟 + 进度提示 | 同步或轮询 | 等待 |
| 适用人数 | 多人同时(群聊) | 一人一次(API) | 一人 |
| 噪声控制 | 需要(指令解析+去重) | 不需要 | 不需要 |
| 开发复杂度 | 中高 | 中 | 低 |
4.2 适用场景
推荐消息机器人:
- 团队日常值班和故障排查
- 内部知识问答(对接知识库)
- 自动化工作流触发(部署、审批、发布)
- 多人协作场景(群聊中共享 AI 能力)
推荐 Web API:
- 前端应用集成
- 需要同步返回结果的场景
4.3 注意事项
- 消息大小限制:飞书/Slack 单条消息通常限制在 3000-5000 字符。Agent 输出过长时间自动截断并提示"完整报告请查看链接"。
- Webhook 必须在 3 秒内返回 200:否则平台会认为失败并重试。所有重逻辑放后台线程。
- Token 安全:Bot 的 API Token 不要硬编码在代码中——用环境变量注入。
- 群聊权限管理:不是所有群聊都允许机器人加入——需要管理员审批和权限配置。
4.4 常见踩坑经验
案例1:Webhook 超时导致平台重复推送消息
现象:同一故障,机器人收到了 3 条相同的 Webhook 事件——处理了 3 次。
根因:Webhook 处理超过了 3 秒,平台认为失败并重试。
解决:Webhook 立即返回 200(不等待分析完成)+ 去重机制兜底。
案例2:机器人回复太频繁导致群聊炸锅
现象:一个故障触发了机器人 3 条消息(确认+进度+结果),用户觉得太吵。
根因:没有合并消息——每步单独发一条。
解决:用消息线程(thread)回复——所有回复都在一条消息的回复链中,不刷主频道。
案例3:指令解析把闲聊识别为故障——凌晨 3 点发了一堆排查建议
现象:群聊里有人说"这个 bug 修得我崩溃了"——机器人识别为"故障",凌晨 3 点认真地回复了排查建议。
根因:意图分类过于宽松——把口语化的"崩溃"当成了系统故障。
解决:增加置信度阈值——意图分类结果需要有足够多的故障关键词(至少 2 个)才触发分析。
4.5 思考题
-
机器人同时收到了 5 条故障报告(5 个不同的人),但 LLM API 每分钟只能处理 3 次调用。你如何设计排队和优先级——让紧急故障(P0)插队到普通故障(P3)前面?
-
如果机器人的分析结果被用户标记为"没用"(点踩),你如何将这个反馈回流到评估体系(第21章)中,持续改进分析质量?
答案将在后续章节揭晓。
延伸阅读与资源
10倍开发者的 Dify 魔法书:从零构建全栈 AI 应用
后端工程师转型AI第一课-Ollama 与私有化大模型实战
大型语言模型(LLM) vLLM 高性能推理落地实战
Agent开发之LlamaIndex 实战修炼与源码进阶
大语言模型Transformers 实战修炼与源码剖析
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)