作者:卢建晖

排版:Alan Wang

想象一下这样的通勤场景:傍晚下班途中,你打开手机上的一个网页,听到两位主播正在播报一档内容连贯的新闻节目。屏幕底部滚动显示着新闻标题和时间戳。点击某条标题,音频就会跳转到对应的新闻内容;点击新闻来源,则会单独打开原始报道。切换到英语,你听到的仍然是基于同一组事实生成的新闻,而不是另一份毫不相关的新闻选集。

这就是 Meridian 24 的构想:一档每半小时更新一次的中国及国际新闻播客,提供中文和英文音频,并通过一个由 HTML、JavaScript 和 CSS3 构建的单页面呈现。

乍一听,这似乎是一个简单的实现方案:搜索新闻,让模型编写播报稿,再将脚本转换成语音。

但真正开始构建后,我们逐渐发现,真正的挑战并不在于让 AI 开口说话,而在于如何让一个小型新闻编辑部以负责任的方式持续运转。

为什么引入 Microsoft Copilot Harness?

第一版新闻脚本需要的是更完善的流程,而不是更漂亮的句子

给模型一堆新闻标题,它就能生成一堆新闻摘要。但这些新闻之间可能缺乏联系。有些搜索结果提供的信息过于有限,模型便可能通过添加评论来填补内容空白。某一家新闻机构也可能因为其报道更容易被检索到,而在节目中占据主导地位。

仅仅再添加一条“让表达听起来更专业”的指令,并不能解决这些问题。

我们需要的是一套编辑方法:收集公开报道、整合相关事件、识别信息缺口、针对缺失信息开展进一步检索、整理有证据支撑的事实,然后才能编写一档由两位主播共同播报、内容连贯的新闻节目。

模型可以成为一名得力的编辑,但一个新闻编辑部仍然需要工作台、资料归档系统、任务分配机制和工作交接流程。

Microsoft Copilot Harness 为这项工作提供了托管式执行基础。Microsoft Foundry 负责运行智能体循环和沙盒环境,协调模型与工具的执行步骤,并支持原生子智能体、文件操作和可复用技能等能力。

开发者因此可以减少搭建新闻编辑部基础设施所花费的时间,将更多精力投入到如何让它真正发挥作用上。

一项在 22:30 启动的制作任务,不应该因为中断而丢失进度

一档新闻节目涉及信息检索、编辑任务、语音合成和音频编码等多个环节。如果服务在已经完成 10 段语音合成后停止运行,重新执行所有步骤就会浪费时间和成本。

这里有两项重要能力,需要明确区分。

Microsoft Copilot Harness 负责运行托管式编辑执行环境。Hosted Agents 的长时间运行恢复能力则为中断任务的重新启动提供基础。至于需要保存哪些进度、如何恢复任务,以及如何避免重复发布,仍然由应用程序决定。

因此,我们没有将整个新闻电台都塞进一个提示词中,也没有将“托管式”理解为“不再需要任何工程开发”。

如何使用 Microsoft Copilot Harness 构建应用?

一个托管式编辑部,以及一个独立的音频制作室

现有的 Hosted 服务已经具备调用 WebIQ、执行语音合成、运行 FFmpeg 以及向 Blob 写入数据的能力。我们没有替换这套服务,而是保留了其中这些确定性的职责,同时使用 Microsoft Copilot Harness 创建了一个独立的 Prompt Agent,专门负责新闻研究和编辑工作。

图 1:紫色边界内是托管式编辑执行环境。WebIQ 和音频制作运行在外部 Hosted 服务中。重要结果会存储在 Blob 中,而不是交由一个无法保证永久可用的沙盒保存。

Microsoft Copilot Harness 负责编辑执行工作,包括研究、整理、任务委派和稿件撰写。Hosted 代码则负责外部 API 调用、验证、语音合成和发布。

这一架构边界也与当前预览版的使用要求有关。在首次公开预览阶段,不能直接将现有智能体切换到另一种 Harness。因此,我们创建了一个新的编辑智能体,同时保留了原有 Hosted Agent 的类型和职责。

下面是项目定义文件中的一段简化示例,省略了完整的指令和自定义新闻工具:

kind: prompt
name: newsradio24-editor
model: gpt-6.1-sol
harness:
  type: microsoft_copilot_preview
tools:
  - type: microsoft_copilot_toolset_preview
    defaultConfig:
      enabled: false
    configs:
      - name: filesystem_read
        enabled: true
      - name: filesystem_write
        enabled: true
      - name: shell
        enabled: true
      - name: subagents
        enabled: true

gpt-6.1-sol 是本项目中使用的模型部署名称,并不意味着它在每个项目中都可以使用。请使用已经部署在你所在项目区域、且受到支持的模型。完整配置请参阅 newsroom.yaml,并通过官方文档确认当前预览版支持的字段和工具。

子智能体是幕后编辑,而不是额外的主播

听众听到的始终只有两位主播。在幕后,整个工作流程包含三个逻辑角色:在这里插入图片描述

主编辑负责分配边界明确的任务,并整合各项任务的结果。该项目规定,每次编辑请求最多执行三个子任务。这并不意味着部署了三个独立运行、永久在线的服务,也不意味着每个阶段都会实例化完全相同的三个子智能体。

沙盒相当于编辑的工作台:Shell 和文件工具可以帮助整理 JSON、检查证据 ID 和选题顺序。每段对话都有独立的工作空间,但这个工作空间并不是持久化档案。重要输出必须存储在持久化存储中。

目前,新闻电台通过版本控制的指令和请求级流程来描述其工作方法。项目尚未将这些流程发布为独立的 Foundry Skills。 可复用技能是平台支持的扩展方向,但我们不应该声称当前实现已经使用了这项能力。

让工具提供证据,而不是凭证

编辑智能体通过 collect_news 和 expand_news 请求新闻发现和内容扩展。这两个工具都接受空对象作为输入。服务器负责绑定新闻时间窗口和上下文,而 WebIQ 密钥则保留在 Hosted 服务中。

当外部函数调用到达时,Hosted 代码负责执行搜索,并在同一段对话中返回带有匹配 call_id 的 function_call_output。这样,编辑智能体就可以继续工作,而无需接触任何凭证。

下面这段代码仅展示编辑请求的入口,并非完整的工具执行循环。payload 携带当前阶段的操作流程、证据和输出约定:

import json
import os

from azure.ai.projects.aio import AIProjectClient
from azure.identity.aio import DefaultAzureCredential


async def request_editor(payload):
    async with (
        DefaultAzureCredential() as credential,
        AIProjectClient(
            endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
            credential=credential,
            allow_preview=True,
        ) as project,
        project.get_openai_client(
            agent_name="newsradio24-editor",
            timeout=900,
            max_retries=2,
        ) as client,
    ):
        conversation = await client.conversations.create()
        return await client.responses.create(
            conversation=conversation.id,
            input=json.dumps(payload, ensure_ascii=False),
        )

本项目使用的是 azure-ai-projects 2.9.0。请从服务器端环境中读取你自己的项目终结点。切勿在文章、浏览器代码或沙盒中放入密钥、真实资源组名称或完整的环境配置文件。完整适配器代码请参阅 harness.py。

相较于自行构建的智能体运行框架,它有哪些优势?

作为一名技术布道师,我更倾向于描述它能够减少哪些工作,而不是暗示其他方案无法实现这些能力。

开源框架和自定义系统同样可以支持工具调用、任务委派、状态管理和沙盒环境。两者的区别在于,Microsoft Copilot Harness 将部分执行基础设施交由 Foundry 管理,并将其与平台的智能体定义、身份和工具配置连接起来。
在这里插入图片描述

对于新闻电台而言,这种权衡意味着工程团队可以将更多时间投入到证据质量、编辑内容的连贯性和听众体验上。

但这并不意味着报道会自动变得真实,也不意味着可以省略权限配置,或者支持任意模型和工具。如果你需要控制框架、依赖项、进程模型或专用中间件,那么 Hosted Agent 仍然是一个合理的选择。

成本也不会因此消失。模型推理、语音、信息检索、存储、流量和运行时使用都需要纳入预算。具体费用请参考当前所在区域的服务定价和相关协议。

它的优势并不是“所有工程工作都有人替你完成”,而是让你有更清晰的机会,将工程精力投入到真正能够体现应用差异化的地方。

构建全天候新闻电台,哪些技术细节最重要?

先将新闻报道转化为事实,再将事实转化为声音

我们的制作流程不会直接将原始搜索结果发送给语音合成服务,而是先生成明确的中间交付成果:

图 2:每个蓝色节点都有可检查的数据结构。当发现研究信息缺口时,系统会触发内容扩展循环。编辑执行和音频制作分别设置独立的验证关卡。

WebIQ 会从新华社、美联社、法新社和彭博社检索公开报道。我们会核查来源和日期。新闻机构主页、公告以及只有标题的搜索结果,都只是发现新闻线索的起点,不能作为充分的事实证据。

相关报道会被整合为新闻主题,缺失的信息则会转化为有针对性的搜索任务。如果扩展检索后仍然找不到足够的证据,就应该缩短报道或直接舍弃,而不是用主观评论填补内容空白。如果某个板块缺乏足够的事实依据,就不应该发布节目。

“热门”同样需要一个准确的定义:当前实现根据相关性、时效性和跨来源报道覆盖情况进行选题,并不具备经过实际测量的受众热度排行榜。

媒体机构之间的平衡应该建立在证据之上,而不是简单地更换标签

早期节目更倾向于新华社的报道,因为能够检索到的可用报道更多。解决办法并不是把这些报道标记成“美联社”,而是为报道数量相对较少的新闻机构预留候选选题空间,并增加有明确范围限制的扩展搜索。

最终选题最多保留两条中国新闻和四条国际新闻。当选出的可靠非新华社新闻至少有两条时,新华社新闻的占比最多为节目的一半。如果证据不足,最终的媒体来源构成仍然可能不够均衡。页面会如实展示实际使用的新闻来源,而不是虚构四家媒体均有报道的情况。

新闻中立性也不是通过让男女主播交换相反观点来实现的。他们分别补充有引用依据的事实和背景信息,完整讲述一条新闻后再进入下一条,并通过自然的过渡衔接内容,而不是刻意制造争论。

两种语言应该共享同一套事实基础

如果中文和英文分别独立选择新闻,最终就可能变成两档完全不同的节目。

我们的做法是先整理一份有序的事实简报,编写中文正文,再根据同一份简报和中文内容改编英文版本。对于节目开场、AI 语音披露、问候语和礼貌性的结束语,则由确定性代码统一添加,而不是寄希望于模型每次都能记住所有要求。

中文主播使用 MAI Voice 2.1 中的 Wei 和 Mei 音色;英文主播使用 Grant 和 Harper。该新闻电台不设体育新闻板块,也没有第三位主播。

“语音太慢”实际上也是一个时间控制问题

Wei 的中文播报语速需要更快。我们没有在浏览器中加速整档节目,而是在服务器端对 Wei 合成的音轨应用了保留音高的 1.25 倍速处理。Mei 和两位英文主播的语速则保持在 1.0 倍。

这一调整意味着,在计算每条新闻的起始时间、组装音频和进行编码之前,必须重新测量处理后的音轨时长。否则,页面上的“跳转到第二条新闻”按钮就可能定位到错误的句子。

WAV 文件是工作文件。最终的 MP4 文件中包含真正的 AAC 音频,而不是仅仅修改了扩展名的 MP3 文件。完整新闻节目及其每个板块的时长都必须控制在 1,200 秒以内,并以实际音频时长为准。对于超出时长限制的输出,系统会直接拒绝,而不是从一句话中间强行截断。

检查交接记录,而不是相信 AI 声称自己已经完成任务

新闻电台会保存信息收集、选题整理、内容扩展、最终简报、中文脚本和英文脚本等阶段的记录。四个编辑阶段都必须实际执行原生子智能体和 Shell 操作。仅仅返回一句“我已经检查过了”是不够的。

下面通过一段简化代码展示其中两个验证关卡。制作流程会检查各阶段的运行时记录,并在音频制作完成后检查节目时长:

observed = harness.native_tools(response_outputs)
if not {"task", "shell"} <= observed:
    raise RuntimeError("Missing native editorial execution")

audio.check_duration(chapters, total_seconds)

原生子任务和 Shell 操作通过平台运行,不能将它们误认为仍然需要 Hosted 代码执行的未解决外部函数调用。该实现依据实际响应事件和 call_id 配对关系进行验证,而不是依赖模型对自身执行情况的评价。

在一次完整的端到端运行中,中文音频的实测时长为 268.1 秒,英文音频为 271.0 秒。验证覆盖了全部六个阶段的记录,以及根据实际 WAV 文件计算出的新闻起始时间。这些只是某一期节目的实际运行结果,并不代表未来的延迟、质量或可用性都能得到保证。

通过这些验证关卡,只能说明系统满足了结构、引用和执行方面的要求,并不能证明内容绝对真实或在语义层面完全中立。人工编辑审核仍然十分重要。

将恢复机制融入数据管理,将发布流程设计成交接机制

已完成的证据、脚本、WAV 文件和状态信息都保存在私有 Blob 存储中。任务恢复时,系统会读取检查点并复用已经合成的音轨,而不是重新执行所有已完成的操作。

只有在中文和英文版本全部完成后,发布租约才会保护目录更新。通过一次写入操作,就可以同时公开两个版本。

定时节目和手动添加的额外节目使用独立目录,因此,手动发布成功不能被当作定时任务成功运行的证明。

我们还遇到了一个非常实际的部署问题:Hosted 服务的实例身份缺少在项目中创建托管式编辑对话的权限。解决办法是在项目范围内,为正确的调用身份分配 Foundry User 角色,并等待权限传播完成。请注意,用户账户拥有相应权限,并不意味着正在运行的智能体也拥有这些权限。

午夜意味着真正的工作交接,而不是重新播放一次

“只保留一天的内容”首先需要明确这一天的定义。我们使用的是当前 Asia/Shanghai 时区的自然日,而不是滚动计算的 24 小时,也不是根据每位访问者所在的本地日期来删除共享 Blob 内容。

每天北京时间午夜,系统都会运行一个独立任务,清理过期目录、公开和私有音频、证据、脚本、WAV 文件及运行时记录,包括快照和已存储的版本。每次节目制作时也会执行一次清理扫描,以处理之前遗漏的清理任务。带日期的文件写入和清理操作共享同一个发布租约,从而避免旧任务在文件删除后重新创建昨天的文件。

即使目录刷新失败,浏览器也会在日期切换时停止播放过期音频。如果当天的第一档节目尚未准备就绪,播放器就会等待。只有在当前新闻电台所属日期内,才允许重复播放最近一档已经完成的节目。

清理操作不会悄悄关闭账户级别的 Azure 软删除保护。如果启用了这项功能,可恢复的文件副本仍然会受到相应策略的约束。已经下载到本地的文件则无法被收回。

最后的细节,都体现在手机屏幕上

前端不需要复杂的框架。一个轻量级单页面就可以实现常驻播放器、新闻标题滚动栏、新闻来源链接、语言切换以及当天节目的按需收听。

点击新闻标题可以跳转到对应音频,点击新闻来源则会打开原始报道。将这两个操作分开,可以避免用户点击时产生歧义。

页面显示的时间应使用听众浏览器所在的时区,而文件路径和清理操作仍然使用北京时间。目录每 45 秒刷新一次。还需要考虑浏览器的自动播放限制,通常用户需要先进行一次点击操作才能开始播放。仅在桌面端进行演示是不够的,还需要实际测试移动端的音频播放、页面滚动、音频定位和时区转换。

结语:一个有用的智能体,不只是给出漂亮的答案

这个项目带来的启发,并不只是 AI 能够自动生成播客节目,而是我们为新闻电台明确了各项职责,建立了持久化的数据存储,并设计了可以检查的工作交接机制。

Microsoft Copilot Harness 为托管式编辑智能体提供工具、工作台和任务委派能力。应用程序代码则负责保障证据、权限、时长、发布和生命周期等边界。

这才是我作为一名技术布道师希望传达的智能体理念:让智能融入真实的工作流程,而不是将整个工作流程隐藏在一个提示词中。

欢迎访问 Meridian 24 页面,了解当前节目并查看交互式数据节点图。该页面只保留当前新闻电台所属日期的内容,并不是一个永久性的节目档案库。

源代码仓库:

https://github.com/kinfey/MSCopilotHarness.NewsPodcast

在这里插入图片描述

参考资料

Logo

微软开发者社区,邀请来自微软以及技术社区专家,带来最前沿的技术干货与实践经验。在这里,您将看到深度教程、最佳实践和创新解决方案。

更多推荐