Contextual Campaign Matching for the Agentic Web
🐦 Follow Updates • 📧 Contact & Feedback
赞助商流 是LLM代理的语义赞助引擎。它使用基于意义的匹配,而不是脆弱的关键字规则,将上下文活动创意注入人工智能交互中。
概述
SponsorStream在本地嵌入会话上下文(FastEmbed),向Qdrant查询候选创意,然后应用类型化定位、策略门控、日程安排和节奏。MCP表面有意设计得较小且安全。
主要亮点:
- 引擎/工作室拆分:代理运行时引擎是只读的;Studio处理配置和摄取。
- 活动+创意模式:一个活动,许多创意,共享目标/政策/时间表。
- 日程安排和节奏:启动/结束窗口以及实时分析的自适应步调。
- SQLite分析:快速本地报告、活动摘要、节奏输入。
先决条件
安装
# uv (recommended)
uv sync
# pip (editable for development)
pip install -e .
# or standard install
pip install .快速入门
# 1) Start Qdrant
docker run -d --name qdrant -p 6333:6333 -p 6334:6334 qdrant/qdrant
# 2) Create collection
uv run sponsorstream-cli create
# 3) Seed sample campaigns
uv run sponsorstream-cli seed
# 4) Start Engine (LLM-facing)
uv run sponsorstream-engine
# 5) Start Studio (admin)
uv run sponsorstream-studio
# 6) View analytics
uv run sponsorstream-cli report --since-hours 24示例活动已在 data/test_ads.json (活动/创意方案)。重新 seed 不安。
建筑
| 表面 | 目的 | 谁叫它 | 入口点 |
|---|---|---|---|
| 发动机 | 匹配、只读检索 | LLM/代理 | uv run sponsorstream-engine |
| 工作室 | 配置、摄取、管理操作 | 人员、CI/CD | uv run sponsorstream-studio 或 uv run sponsorstream-cli |
发动机工具(LLM饰面)
campaigns_match--语义匹配(context_text、约束、top_k);返回候选者+match_idcampaigns_explain--先前匹配的审核跟踪campaigns_health--活性/准备状态campaigns_capabilities--布局、约束、嵌入模型、模式版本
工作室工具(管理员)
collection_ensure--创建/对齐集合collection_info--集合元数据collection_migrate--可选的架构迁移campaigns_upsert_batch--批量活动/创意摄入creatives_delete--删除创意campaigns_bulk_disable--按筛选器禁用创意creatives_get--获取创意campaigns_report--分析摘要或活动报告
活动模式(摘录)
{
"campaign_id": "camp-001",
"advertiser_id": "adv-tech",
"name": "Python Mastery",
"creatives": [
{
"creative_id": "cr-001-a",
"title": "Learn Python Today",
"body": "Master Python programming...",
"cta_text": "Start Learning",
"landing_url": "https://example.com/python"
}
],
"targeting": {
"topics": ["python", "education"],
"locale": ["en-US"],
"audience_segments": ["developers"],
"keywords": ["python", "ai"]
},
"policy": {
"sensitive": false,
"age_restricted": false,
"brand_safety_tier": "high"
},
"schedule": {
"start_at": "2024-01-01T00:00:00+00:00",
"end_at": "2030-01-01T00:00:00+00:00"
},
"budget": {
"daily_budget": 50.0,
"total_budget": 1000.0,
"pacing_mode": "adaptive",
"cpm": 12.0,
"target_ctr": 0.1
}
}配置
环境变量(或 .env)启动时验证:
| 变量 | 默认值 | 描述 |
|---|---|---|
QDRANT_HOST | localhost | Qdrant服务器主机 |
QDRANT_PORT | 6333 | Qdrant服务器端口 |
QDRANT_COLLECTION_NAME | ads | 收藏名称 |
EMBEDDING_MODEL_ID | BAAI/bge-small-en-v1.5 | 嵌入模型 |
EMBEDDING_DIMENSION | 384 | 矢量维度 |
CREATIVE_ID_NAMESPACE | a1b2... | 创意ID的UUID命名空间 |
MAX_TOP_K | 100 | 每次匹配查询的最大结果数 |
MAX_BATCH_SIZE | 500 | 每个追加销售批次的最大创意 |
REQUEST_TIMEOUT_SECONDS | 30.0 | 每次请求超时 |
REQUIRE_ENGINE_KEY | false | 需要 MCP_ENGINE_KEY |
REQUIRE_STUDIO_KEY | false | 需要 MCP_STUDIO_KEY |
ANALYTICS_DB_PATH | data/analytics.db | SQLite分析路径 |
验证
uv run pytest tests/ -v验证工具是否匹配:
uv run python -c "
from sponsorstream.interface.mcp.server import create_server
from sponsorstream.interface.mcp.tools import ENGINE_ALLOWED_TOOLS
s = create_server('engine')
tools = set(s._tool_manager._tools.keys())
assert tools == ENGINE_ALLOWED_TOOLS
print('Engine tool allowlist OK')
"用法示例
from sponsorstream.domain.sponsorship import Campaign, CreativeSpec
from sponsorstream.models.mcp_requests import MatchRequest
from sponsorstream.wiring import build_index_service, build_match_service
campaign = Campaign(
campaign_id="camp-001",
advertiser_id="adv-1",
name="Python Mastery",
creatives=[
CreativeSpec(
creative_id="cr-001-a",
title="Learn Python Today",
body="Master Python programming...",
cta_text="Start",
landing_url="https://example.com/python",
)
],
)
index_svc = build_index_service()
index_svc.ensure_collection()
index_svc.upsert_campaigns([campaign])
match_svc = build_match_service()
resp, trace = match_svc.match(MatchRequest(context_text="python tutorial", top_k=3))
print(resp.model_dump_json(indent=2))联系
如果您正在构建MCP工具或代理货币化堆栈,请随时联系:
- 🐦 https://x.com/mr19042000
- 📧 邮寄地址:ritesh19@bu.edu
5.验证配置加载和验证
# Defaults
uv run python -c "
from sponsorstream_mcp.config import get_settings
s = get_settings()
print(f'host={s.qdrant_host} port={s.qdrant_port} model={s.embedding_model_id}')
"
# Invalid port fails fast
QDRANT_PORT=99999 uv run python -c "from sponsorstream_mcp.config.runtime import RuntimeSettings; RuntimeSettings()" 2>&1 | head -36.验证导入隔离(数据平面不加载管理代码)
uv run python -c "
import sys
from sponsorstream_mcp.main_runtime import main
mods = [m for m in sys.modules if m.startswith('sponsorstream_mcp')]
assert 'sponsorstream_mcp.cli' not in mods, 'FAIL: cli imported'
print('PASS: main_runtime has clean import graph (no admin modules)')
"添加依赖关系
uv add
# Add a dependency
uv add --dev
# Add a dev dependency广告模式
Qdrant中存储的每个广告都包含:
| 字段 | 类型 | 描述 |
|---|---|---|
ad_id | string | 广告的唯一标识符 |
advertiser_id | string | 广告商的标识符 |
title | string | 广告标题 |
body | string | 广告正文 |
cta_text | string | 行动呼吁文本 |
landing_url | string | 重定向URL |
targeting.topics | string\[\] | 要定位的主题 |
targeting.locale | 字符串\[\] | 本地代码(例如,“en-us”) |
targeting.verticals | string\[\] | 垂直行业 |
targeting.blocked_keywords | string\[\] | 要排除的关键字 |
policy.sensitive | boolean | 敏感内容标志 |
policy.age_restricted | boolean | 年龄限制标志 |
enabled | boolean | 广告是否符合匹配条件(默认 true; ads_bulk_disable 套 false) |
嵌入文本:向量嵌入是从以下内容生成的 title + body + topics.
用法示例
from sponsorstream_mcp.models import Ad, AdTargeting, AdPolicy
from sponsorstream_mcp.wiring import build_index_service, build_match_service
from sponsorstream_mcp.models.mcp_requests import MatchRequest
# Create the collection (once) and seed ads via IndexService
index_svc = build_index_service()
index_svc.ensure_collection()
ad = Ad(
ad_id="ad-001",
advertiser_id="adv-123",
title="Learn Python Today",
body="Master Python programming with our interactive courses.",
cta_text="Start Learning",
landing_url="https://example.com/python",
targeting=AdTargeting(
topics=["programming", "python", "education"],
locale=["en-US"],
verticals=["education", "technology"],
),
policy=AdPolicy(sensitive=False, age_restricted=False),
)
index_svc.upsert_ads([ad])
# Match ads via MatchService (Data Plane logic)
match_svc = build_match_service()
response, audit_trace = match_svc.match(
MatchRequest(context_text="python tutorial", top_k=5)
)
for c in response.candidates:
print(f"{c.ad_id}: {c.title} (score={c.score}, match_id={c.match_id})")ads_match 请求/响应模式
数据平面 ads_match 该工具使用类型化的DTO——不接受原始字典过滤器。
请求参数
| 参数 | 类型 | 默认值 | 说明 | |
|---|---|---|---|---|
context_text | 字符串(1-10000个字符) | *必需的* | 要匹配的对话/页面上下文 | |
top_k | 整数(1-100) | 5 | 返回的候选人数量 | |
placement | 字符串 | "inline" | 放置槽(例如。 inline, sidebar, banner) | |
surface | 字符串 | "chat" | 表面类型(例如。 chat, search, feed) | |
topics | string\[\] | 空 | null | 仅限于这些主题 |
locale | string | 空 | null | 所需的区域设置(例如。 en-US) |
verticals | string\[\] | 空 | null | 仅限于这些垂直领域 |
exclude_advertiser_ids | string\[\] | 空 | null | 要排除的广告商ID |
exclude_ad_ids | string\[\] | 空 | null | 要排除的广告ID |
age_restricted_ok bool的。 false | 允许年龄限制广告 | |||
sensitive_ok bool的。 false | 允许敏感内容广告 |
响应形状
{
"candidates": [
{
"ad_id": "ad-001",
"advertiser_id": "adv-123",
"title": "Learn Python Today",
"body": "Master Python programming...",
"cta_text": "Start Learning",
"landing_url": "https://example.com/python",
"score": 0.95,
"match_id": "m-abc123"
}
],
"request_id": "req-xyz-456",
"placement": "sidebar"
}match_id可以传递给ads_explain用于审计跟踪(为什么符合/不符合条件、过滤器、分数)score是余弦相似度(0-1)
跟我说话
我总是热衷于研究MCP工具、检索系统和实用的LLM货币化。\ 如果你正在构建类似的东西,或者想对你的架构进行压力测试,请联系:
- 🐦 推特:https://x.com/mr19042000
- 📧 电子邮件:mailto:ritesh19@bu.edu主题=赞助商流MCP
