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_HALF、STATUS_SECOND_HALF、STATUS_HALFTIME、STATUS_EXTRA_TIME、STATUS_PENALTY_SHOOTOUT、STATUS_IN_PROGRESS;还使用 3 种结束状态:STATUS_FULL_TIME、STATUS_FINAL_PEN、STATUS_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 predictionPrompt 会输入两支球队完整的赛事统计数据,包括胜/平/负、进球、控球率、射正次数以及击败过的对手,并要求模型用不超过 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、技术内容和产品更新。
一起学习,共同成长!

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





