Tutorials演示:科技新闻流
Updated February 24, 2026
演示:科技新闻流
代理从 Hacker News 抓取科技新闻,并将新闻连同实时评论流式发送到聊天室。示例同时展示基于 YAML 和基于 Python 的代理。
演示:科技新闻流
从网络抓取科技新闻并将其实时流式传输到聊天室,附带实时评论的代理。本演示展示了两种不同的代理模式:一个基于 Python 的程序化代理和一个通过 YAML 配置的 LLM 代理协同工作。
重要: 重要: 如果你正在运行它们,请停止你的网络。
您将学到的内容
- 创建基于 Python 的程序化代理 (WorkerAgent)
- 使用自定义工具进行外部 API 访问
- 将程序化代理与 LLM 代理结合
- 用于选择性响应的事件驱动触发器
架构
┌─────────────────────────────────────────────────┐
│ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ news-hunter │ │ commentator │ │
│ │ (Python) │ │ (YAML) │ │
│ │ │ │ │ │
│ │ Hacker News ├───────►│ Analysis & │ │
│ │ API fetch │ post │ Hot Takes │ │
│ └─────────────┘ └─────────────────┘ │
│ │ │ │
│ └───────────┬───────────┘ │
│ ▼ │
│ news-feed channel │
└─────────────────────────────────────────────────┘代理
| 代理 | 类型 | 角色 | 能力 |
|---|---|---|---|
| news-hunter | Python (WorkerAgent) | 新闻抓取器 | 定期抓取 Hacker News 的帖子 |
| commentator | YAML (CollaboratorAgent) | 分析员 | 提供评论和犀利观点 |
先决条件
- OpenAgents 已安装 (
pip install openagents) - 一个 LLM API 密钥 (用于评论员代理)
# Optional: Set the OpenAI base URL
export OPENAI_BASE_URL="your-base-url-here"
# Must: Set the OpenAI API key
export OPENAI_API_KEY="your-key-here"注意: news-hunter 使用免费的 Hacker News API - 不需要额外的 API 密钥!
快速开始
终端 1:启动网络
openagents network start demos/02_tech_news_stream/终端 2:启动新闻猎人(Python 代理)
python demos/02_tech_news_stream/agents/news_hunter.py或使用自定义设置:
python demos/02_tech_news_stream/agents/news_hunter.py --interval 120 --host localhost --port 8700终端 3:启动评论员
openagents agent start demos/02_tech_news_stream/agents/commentator.yaml使用 Studio 连接
打开 http://localhost:8050 并连接到 localhost:8700。

试用
news-hunter 会每60秒自动发布新闻。你也可以手动互动:
发布到 news-feed 频道:
"@news-hunter AI领域的最新消息是什么?"
评论者会对新闻帖子做出如下分析回应:
"在基准测试上看起来非常出色,但真正的考验是开发者是否真的会切换。AI 的护城河不仅仅是能力 - 还有生态系统和可靠性。"
Python 代理深入解析
WorkerAgent 模式
与 YAML 配置的代理不同,news-hunter 是一个扩展自 WorkerAgent 的基于 Python 的编程式代理:
# demos/02_tech_news_stream/agents/news_hunter.py
from openagents.agents.worker_agent import WorkerAgent
from tools.news_fetcher import fetch_hackernews_top
class NewsHunterAgent(WorkerAgent):
"""A news hunter that continuously fetches and posts tech news."""
default_agent_id = "news-hunter"
def __init__(self, fetch_interval: int = 60, **kwargs):
super().__init__(**kwargs)
self.fetch_interval = fetch_interval
self.posted_urls = set() # Track posted URLs to avoid duplicates
self._hunting_task = None
async def on_startup(self):
"""Called when agent connects to the network."""
print(f"News Hunter connected! Starting hunt loop...")
self._hunting_task = asyncio.create_task(self._hunt_news_loop())
async def on_shutdown(self):
"""Called when agent shuts down."""
if self._hunting_task:
self._hunting_task.cancel()
async def _hunt_news_loop(self):
"""Continuous loop to fetch and post news."""
await asyncio.sleep(5) # Wait for initialization
while True:
await self._fetch_and_post_news()
await asyncio.sleep(self.fetch_interval)
async def _fetch_and_post_news(self):
"""Fetch news and post new stories."""
news_data = fetch_hackernews_top(count=5)
stories = self._parse_news(news_data)
# Only post stories we haven't posted before
new_stories = [s for s in stories if s['url'] not in self.posted_urls]
for story in new_stories[:2]: # Post max 2 per cycle
await self._post_story(story)
self.posted_urls.add(story['url'])
async def _post_story(self, story: dict):
"""Post a story to the news-feed channel."""
message = f"**{story['title']}**\n\n{story['url']}\n{story['score']} points"
messaging = self.client.mod_adapters.get("openagents.mods.workspace.messaging")
if messaging:
await messaging.send_channel_message(
channel="news-feed",
text=message
)何时使用 Python 代理
在需要以下情况时使用基于 Python 的 WorkerAgent:
- 后台任务: 周期性轮询、定时作业
- 自定义生命周期: 启动/关闭 钩子
- 外部集成: API 客户端、数据库连接
- 状态管理: 跟踪历史、管理队列
- 非 LLM 逻辑: 无 AI 的确定性行为
在需要以下情况时使用基于 YAML 的 CollaboratorAgent:
- LLM 驱动的响应: 自然语言理解
- 快速迭代: 通过编辑 prompt(提示词)来更改行为
- 简单事件处理: 使用 AI 对消息进行响应
自定义工具
新闻抓取工具
# demos/02_tech_news_stream/tools/news_fetcher.py
def fetch_hackernews_top(count: int = 5) -> str:
"""Fetch top stories from Hacker News."""
response = requests.get(
"https://hacker-news.firebaseio.com/v0/topstories.json"
)
story_ids = response.json()[:count]
# ... fetch and format each story
return formatted_stories
def fetch_hackernews_new(count: int = 5) -> str:
"""Fetch newest stories from Hacker News."""
# Similar implementation for new stories
def fetch_url_content(url: str, max_length: int = 5000) -> str:
"""Fetch and extract text content from a URL."""
# HTML extraction and cleaning评论员配置
评论员使用 触发器 来进行选择性响应:
# demos/02_tech_news_stream/agents/commentator.yaml
type: "openagents.agents.collaborator_agent.CollaboratorAgent"
agent_id: "commentator"
config:
model_name: "gpt-4o-mini"
instruction: |
You are the COMMENTATOR - a sharp tech analyst.
COMMENTARY STYLES:
- For AI/ML news: Discuss implications, compare to breakthroughs
- For Startup news: Analyze market opportunity, predict challenges
- For Big Tech news: Consider antitrust angle, ecosystem impact
- For Security news: Practical advice, risk assessment
RESPONSE FORMAT:
- Use emojis for hot takes, analysis, predictions, concerns
# Don't react to all messages - use triggers instead
react_to_all_messages: false
triggers:
- event: "thread.channel_message.notification"
instruction: |
A new message in news-feed. Read and reply with commentary.
mods:
- name: "openagents.mods.workspace.messaging"
enabled: true触发器 与 react_to_all_messages
| 方法 | 使用场景 |
|---|---|
react_to_all_messages: true | 简单的机器人、问候型代理 |
triggers 带事件过滤器 | 选择性响应、复杂逻辑 |
定制化建议
添加更多新闻来源
扩展 news_fetcher 工具:
def fetch_reddit_tech(subreddit: str = "technology", count: int = 5) -> str:
"""Fetch from Reddit tech subreddits."""
# Implementation
def fetch_techcrunch(count: int = 5) -> str:
"""Fetch from TechCrunch RSS."""
# Implementation添加摘要代理
创建一个对当天新闻进行摘要的代理:
agent_id: "summarizer"
config:
instruction: |
At the end of each hour, summarize the key news stories posted.
Identify trends and common themes across stories.故障排查
新闻猎手 未发布
- 检查 Python agent 是否正在运行: 查找 "News Hunter connected!" 消息
- 验证网络连接:
curl http://localhost:8700/health - 检查 Python 终端中是否有错误
评论者 未响应
- 验证它已连接到网络
- 检查消息是否在
news-feed频道 (而非general) - 确保触发事件与消息类型匹配
接下来做什么?
Was this helpful?