← 返回AI变现
🌐 其他

2026 世界杯:一个通过与 CoCo 对话构建的实时应用 | 技术实践

来源:InfoQ 中文 · 发布于 2026-08-20 01:12:42
2026 世界杯:一个通过与
2026 年,智能体将在企业级应用中取得哪些实质性突破?点击下载《2026 年 AI 与数据发展预测》白皮书,获悉专家一手前瞻,抢先拥抱新的工作方式!我构建了一个 FIFA World Cup 2026 应用,它包含实时比分、淘汰赛对阵生成器、交锋统计、场馆地图、比赛预测等功能,而且整个过程几乎完全是通过与 Corte…

2026 年,智能体将在企业级应用中取得哪些实质性突破?点击下载《2026 年 AI 与数据发展预测》白皮书,获悉专家一手前瞻,抢先拥抱新的工作方式!

我构建了一个 FIFA World Cup 2026 应用,它包含实时比分、淘汰赛对阵生成器、交锋统计、场馆地图、比赛预测等功能,而且整个过程几乎完全是通过与 Cortex Code Desktop(CoCo)的一系列自然语言对话完成的。

之所以花了几周时间,是因为一开始我完全没有任何样板代码;同时随着赛事推进,我也不断想把新的想法、更好的交互和更多实时统计数据迭代进来。

首页就是一个典型例子:它最初只是一个静态倒计时卡片,后来通过纯对话式开发,逐渐长成一个实时 dashboard。

  • 一开始它只有四个指标卡片(距离开赛天数、剩余比赛场次、48 支球队、16 个场馆)和一个赛程 dataframe;

  • 后来又加上了实时比赛卡片,以及带动画的统计 pills(控球率、射门、角球)和进球/黄牌/红牌事件标记;

  • 接着加入了下一场比赛的 JS 倒计时,以及当多场比赛同时开踢时的多卡片堆叠显示;

  • 然后加入了纵向淘汰赛 bracket,并叠加了以金色 badge 呈现的 AI 预测;

  • 再后来加入了比赛进行中的实时胜率条,以及带赛事统计摘要的球队卡片;

  • 最后,又加上了冠军庆祝卡片和 confetti 动效,用在 World Cup 2026 决赛结束之后。

其他页面也经历了类似过程,只是迭代次数可能略少,例如:

技术栈

GitHub → Streamlit Community Cloud (auto-deploy on push) ├── Snowflake │ ├── TEAMS (48 teams, flags, FIFA rankings) │ ├── VENUES (16 stadiums, lat/lng, capacity) │ └── MATCHES (72 group stage fixtures) ├── ESPN Free API (live scores, no auth) └── Snowflake Cortex AI_COMPLETE (match predictions)
复制代码

这个应用运行在 Streamlit Community Cloud 上,静态赛事数据存储在 Snowflake 中,实时比分和比赛统计来自 ESPN 的公开但未文档化的 API,而 AI 驱动的比赛预测则通过 Snowflake Cortex AI Functions 生成。

开发过程:AI 辅助迭代

从最初脚手架到最后的冠军庆祝卡片,每一个功能几乎都是通过和 CoCo 对话构建出来的。我只需要描述想要什么,然后在生成结果基础上继续迭代。

一个很典型的例子是 predictions.py 模块。这个模块有 200 多行代码,里面包含统计聚合、综合评分计算,以及对 Cortex AI Functions 的集成,而这些内容几乎是在一次对话中完成的。

ESPN 未文档化 API:踩过的一些坑

在使用这个 API 的过程中,我们遇到了几个很典型的问题。

100 条赛事截断

问题:当你查询 ?dates=20260611–20260719 这种覆盖整个赛事周期的时间范围时,ESPN 会悄悄把结果截断在大约 100 场比赛左右。我们是在赛事进行到中段时才发现这个问题,因为半决赛结果竟然“消失”了。

解决办法:把查询拆成两个日期区间。

@st.cache_data(ttl=60)def get_all_results(): results = [] # Split to avoid ESPN's ~100 event cap for date_range in ["20260611–20260627", "20260628-" + _today_str()]: resp = requests.get(ESPN_API, params={"dates": date_range}, timeout=10) # … normalize and collect return results
复制代码

跨午夜边界丢比赛

问题:如果不传 dates 参数,ESPN 默认只返回当前美东时间自然日的数据。这意味着,一场在美东时间晚上 11:55 开始、并在午夜后仍处于补时阶段的比赛,会在跨天后直接从结果里消失。

解决办法:始终查询一个 3 天窗口。

et = pytz.timezone("US/Eastern")now_et = datetime.now(et)yesterday = (now_et - timedelta(days=1)).strftime("%Y%m%d")tomorrow = (now_et + timedelta(days=1)).strftime("%Y%m%d")
复制代码

状态识别

ESPN 使用 6 种不同的进行中状态:STATUS_FIRST_HALFSTATUS_SECOND_HALFSTATUS_HALFTIMESTATUS_EXTRA_TIMESTATUS_PENALTY_SHOOTOUTSTATUS_IN_PROGRESS;还使用 3 种结束状态:STATUS_FULL_TIMESTATUS_FINAL_PENSTATUS_FINAL_AET

只要漏掉其中任何一个,淘汰赛 bracket 就会出错。

不用 WebSockets 也能做到实时更新

我们使用了 Streamlit 的 @st.fragment(run_every=N) 装饰器,它可以按固定间隔只重跑被装饰的函数,而无需整页刷新。

@st.fragment(run_every=10)def _live_section():    live = get_live_matches()    if live:        for i, match in enumerate(live):            _render_live_match(match, i)
复制代码

再结合 API 调用上的 @st.cache_data(ttl=15),就能以极低的 ESPN API 负载实现接近实时的更新。实时比赛区域每 10 秒刷新一次,赛程/结果区域每 60 秒刷新一次,页面顶部 banner 也是每 60 秒刷新一次。

为了防止页面闪烁,我们还处理了一个额外问题:ESPN 偶尔会因为 CDN 抖动返回空结果。为了解决这个问题,我们把最近一次可用状态缓存在 st.session_state 里:

if live:    st.session_state["_last_live"] = liveelif "_last_live" in st.session_state:    live = st.session_state["_last_live"]
复制代码

使用 Snowflake Cortex AI 做比赛预测

预测系统分成两层。

第一层:统计评分

基于真实赛事表现数据计算加权综合评分:

rating = (    win_rate * 35 +        # Points per game normalized    gd_norm * 20 +         # Goal difference per game    poss_norm * 15 +       # Average possession    sot_norm * 15 +        # Shots on target per game    cs_rate * 15           # Clean sheet rate)
复制代码

通过比较两支球队的评分,计算出对应的胜率百分比。这一过程完全基于确定性计算,速度很快,也不需要调用任何 API。

第二层:Cortex AI 推理

至于“为什么会得出这个预测”,我们会将两支球队汇总后的统计数据传入 Snowflake Cortex 的 AI_COMPLETE

query = f"""  SELECT SNOWFLAKE.CORTEX.AI_COMPLETE(    'claude-sonnet-7',    '{prompt_escaped}'  ) AS prediction
复制代码

Prompt 会输入两支球队完整的赛事统计数据,包括胜/平/负、进球、控球率、射正次数以及击败过的对手,并要求模型用不超过 15 个单词的一句话给出预测。

Based on these FIFA World Cup 2026 tournament stats, predict the winnerof France vs Spain in exactly one short sentence (max 15 words). Focuson the key differentiator.France: 4 played, 3W 1D 0L, 9 goals scored, 2 conceded, avg possession57%, 4.8 shots on target/game, 2 clean sheets. Beat: Australia, Nigeria,Colombia.Spain: 4 played, 3W 0D 1L, 8 goals scored, 4 conceded, avg possession62%, 5.2 shots on target/game, 1 clean sheets. Beat: Croatia, Japan,Brazil.
复制代码

此外,这部分结果还设置了 5 分钟的 TTL 缓存,因此不会在每次页面加载时都重新触发模型调用。

实时胜率预测

比赛进行期间,另一套独立的 live_win_probability() 函数会根据实时赛况计算动态胜率,主要考虑以下因素:

比分: 每个进球大约会带来 18 个百分点的胜率变化。

比赛时间: 越接近比赛结束,当前比分的影响越大,预测结果也会越来越向实际赛果收敛。

控球率和射正次数: 作为较小幅度的修正因素,通常会带来约 ±5~8 个百分点的调整。

红牌: 会造成非常明显的胜率变化,每张红牌约带来 -15 个百分点的影响。

goal_diff = s1 - s2if goal_diff == 0:    base1, base2 = 35, 35  # leaves 30% drawelif goal_diff > 0:    shift = min(40, goal_diff * 18)    base1 = 50 + shift    base2 = max(5, 50 - shift - 15)
复制代码

这套胜率会与实时比分一起,每 10 秒更新一次。同时,系统还会生成一句由 Cortex 提供的推理说明,用来解释当前胜率为什么处于这一水平。这条推理结果会缓存 60 秒。

纵向淘汰赛对阵图:在 Streamlit 中使用 JavaScript

淘汰赛对阵图完全使用 HTML/JavaScript 实现,并通过 components.html() 进行渲染。这样既能支持点击选择球队的交互,也能实现自定义页面布局。

其中几个关键设计包括:

按轮次维护并行数组: 比赛日期按轮次分别存储,而不是使用以球队为 Key 的字典。因为如果同时存在多场“TBD vs TBD(待定球队 vs 待定球队)”,使用相同 Key 会发生冲突。

自动隐藏已经结束的轮次: 对阵图会自动聚焦接下来即将进行的比赛,避免页面被已经完成的赛事占满。

金色预测标签: 对于尚未进行的比赛,会显示国旗 Emoji 和对应的预测胜率百分比。

localStorage: 用于保存用户自己填写的淘汰赛对阵预测。

对阵图会自动读取 ESPN 的比赛结果,已经确定赛果的比赛会被锁定,无法再修改。在独立的 Bracket 对阵图页面中,可以通过 show_all_rounds 参数关闭自动隐藏,从而一次性查看完整的淘汰赛赛程。

部署流程:Git Push 即上线

整个部署流程实际上只有一句:

git push origin main

Streamlit Community Cloud 会持续监听这个代码仓库,一旦检测到更新,就会在几秒内自动重新部署。

就这么简单……

不需要 Docker,不需要 CI/CD,也不需要自己管理任何基础设施。

如果重新做一次,我会改进什么

我认为这个应用已经发展得相当完善,目前的状态也不错,不过永远都有继续优化的空间。以下是一些我想到的改进方向。

API 调用:相比直接依赖未公开文档的 API,我会考虑通过 Snowflake External Function 进行代理,并增加降级和重试机制。

复杂交互:如果使用支持双向消息传递的自定义 Streamlit Component,整体实现会更加干净。因为目前 iframe 中的 JavaScript 无法直接把信息传回 Streamlit,只能通过一些比较取巧的 window.parent DOM 操作来实现。

预测缓存:目前设置了 5 分钟 TTL,这意味着一场比赛刚刚结束后,下一轮比赛的预测不会立即更新。采用更智能的缓存失效机制,例如基于比赛结果数量生成 Hash,可以有所改善,但依然做不到完全实时。

代码重构:一个应用永远都还有继续重构的空间 :)

应用地址

可以体验这个在线应用,也欢迎分享给其他人。

可以在 GitHub 获取源代码,并在此基础上进行自己的定制开发。

还可以在 macOS 和 Windows 上开始使用 Cortex Code Desktop,构建属于你自己的应用。

保持联系

感谢你花时间读到这里。希望这篇博客既能让你有所收获,也能为你带来一些启发。

欢迎在 LinkedIn 上与我联系,获取更多 Demo、技术内容和产品更新。

一起学习,共同成长!

原文地址:https://medium.com/snowflake/world-cup-2026-a-real-time-app-built-by-conversations-with-coco-6afef17528da

点击链接立即报名注册:Ascent-Snowflake Platform Training-China,更多 Snowflake 精彩活动请关注专区