feat(configs): add GEO AgentKit Server configuration
- llm_config.yaml: DeepSeek + OpenAI-compatible providers with env var substitution - skills/ (8 YAML): citation_detector, content_generator, deai_agent, geo_optimizer, monitor, schema_advisor, competitor_analyzer, trend_agent - Added intent fields for content_generator, competitor_analyzer, trend_agent - Added quality_gate fields for content_generator, deai_agent, geo_optimizer - Updated custom_handler paths to configs.geo_handlers - geo_tools.py: 14 FunctionTools calling GEO Backend via HTTP - geo_handlers.py: 3 custom handlers (citation/monitor/schema) calling /internal/ API - geo_server.py: FastAPI factory with LLM Gateway, Tool Registry, Skill Registry
This commit is contained in:
parent
47a848fbcb
commit
669ca604e5
|
|
@ -0,0 +1 @@
|
||||||
|
"""GEO AgentKit Server 配置包"""
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
"""GEO 项目的 Custom Handler — 供 AgentKit Server 使用
|
||||||
|
|
||||||
|
所有 Handler 通过 HTTP 回调 GEO Backend 的 /internal/ 端点,不直接访问 DB。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from agentkit.core.protocol import TaskMessage
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
GEO_BACKEND_URL = os.getenv("GEO_BACKEND_URL", "http://localhost:8000")
|
||||||
|
INTERNAL_API_TOKEN = os.getenv("INTERNAL_API_TOKEN", "")
|
||||||
|
|
||||||
|
|
||||||
|
def _internal_headers() -> dict:
|
||||||
|
"""获取内部 API 请求头"""
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
if INTERNAL_API_TOKEN:
|
||||||
|
headers["X-Internal-Token"] = INTERNAL_API_TOKEN
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_citation_task(task: TaskMessage) -> dict:
|
||||||
|
"""引用检测任务 — 通过 HTTP 回调 GEO Backend
|
||||||
|
|
||||||
|
task_type 路由:
|
||||||
|
- citation_detect: POST /internal/citation/detect
|
||||||
|
- citation_detect_single: POST /internal/citation/detect-single
|
||||||
|
"""
|
||||||
|
if task.task_type == "citation_detect":
|
||||||
|
return await _call_internal("/internal/citation/detect", task.input_data)
|
||||||
|
elif task.task_type == "citation_detect_single":
|
||||||
|
return await _call_internal("/internal/citation/detect-single", task.input_data)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported task type: {task.task_type}")
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_monitor_task(task: TaskMessage) -> dict:
|
||||||
|
"""效果追踪任务 — 通过 HTTP 回调 GEO Backend
|
||||||
|
|
||||||
|
task_type 路由:
|
||||||
|
- monitor_track: POST /internal/monitor/track
|
||||||
|
- monitor_check_single: POST /internal/monitor/check-single
|
||||||
|
"""
|
||||||
|
if task.task_type == "monitor_track":
|
||||||
|
return await _call_internal("/internal/monitor/track", task.input_data)
|
||||||
|
elif task.task_type == "monitor_check_single":
|
||||||
|
return await _call_internal("/internal/monitor/check-single", task.input_data)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported task type: {task.task_type}")
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_schema_task(task: TaskMessage) -> dict:
|
||||||
|
"""Schema 建议任务 — 通过 HTTP 回调 GEO Backend
|
||||||
|
|
||||||
|
task_type 路由:
|
||||||
|
- schema_advise: POST /internal/schema/advise
|
||||||
|
"""
|
||||||
|
if task.task_type == "schema_advise":
|
||||||
|
return await _call_internal("/internal/schema/advise", task.input_data)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported task type: {task.task_type}")
|
||||||
|
|
||||||
|
|
||||||
|
async def _call_internal(path: str, input_data: dict) -> dict:
|
||||||
|
"""调用 GEO Backend 内部 API"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=300.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{GEO_BACKEND_URL}{path}",
|
||||||
|
json=input_data,
|
||||||
|
headers=_internal_headers(),
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
logger.error(f"HTTP error calling {path}: {e.response.status_code} {e.response.text[:500]}")
|
||||||
|
return {"error": f"HTTP {e.response.status_code}", "detail": e.response.text[:500]}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calling {path}: {e}")
|
||||||
|
return {"error": str(e)}
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
"""GEO AgentKit Server 启动入口
|
||||||
|
|
||||||
|
工厂函数 create_geo_app() 初始化 LLM Gateway、Tool Registry、Skill Registry,
|
||||||
|
然后创建 FastAPI 应用。
|
||||||
|
|
||||||
|
使用方式:
|
||||||
|
uvicorn configs.geo_server:create_geo_app --factory --host 0.0.0.0 --port 8001
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from agentkit.core.agent_pool import AgentPool
|
||||||
|
from agentkit.llm.config import LLMConfig
|
||||||
|
from agentkit.llm.gateway import LLMGateway
|
||||||
|
from agentkit.llm.providers.openai import OpenAICompatibleProvider
|
||||||
|
from agentkit.quality.gate import QualityGate
|
||||||
|
from agentkit.quality.output import OutputStandardizer
|
||||||
|
from agentkit.router.intent import IntentRouter
|
||||||
|
from agentkit.server.app import create_app
|
||||||
|
from agentkit.skills.loader import SkillLoader
|
||||||
|
from agentkit.skills.registry import SkillRegistry
|
||||||
|
from agentkit.tools.registry import ToolRegistry
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# ─── 配置路径 ───
|
||||||
|
|
||||||
|
CONFIGS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
LLM_CONFIG_PATH = os.path.join(CONFIGS_DIR, "llm_config.yaml")
|
||||||
|
SKILLS_DIR = os.path.join(CONFIGS_DIR, "skills")
|
||||||
|
|
||||||
|
|
||||||
|
def _substitute_env_vars(config_path: str) -> dict:
|
||||||
|
"""加载 YAML 配置并替换 ${VAR} 环境变量"""
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
with open(config_path, encoding="utf-8") as f:
|
||||||
|
raw = f.read()
|
||||||
|
|
||||||
|
# 递归替换 ${VAR_NAME} 和 ${VAR_NAME:-default} 格式
|
||||||
|
import re
|
||||||
|
def _replace_env(match):
|
||||||
|
var_expr = match.group(1)
|
||||||
|
if ":-" in var_expr:
|
||||||
|
var_name, default = var_expr.split(":-", 1)
|
||||||
|
return os.getenv(var_name, default)
|
||||||
|
return os.getenv(var_expr, match.group(0))
|
||||||
|
|
||||||
|
resolved = re.sub(r"\$\{([^}]+)\}", _replace_env, raw)
|
||||||
|
return yaml.safe_load(resolved)
|
||||||
|
|
||||||
|
|
||||||
|
def _init_llm_gateway() -> LLMGateway:
|
||||||
|
"""初始化 LLM Gateway 并注册 Provider"""
|
||||||
|
config_data = _substitute_env_vars(LLM_CONFIG_PATH)
|
||||||
|
config = LLMConfig.from_dict(config_data)
|
||||||
|
|
||||||
|
gateway = LLMGateway(config)
|
||||||
|
|
||||||
|
for provider_name, pconf in config.providers.items():
|
||||||
|
if not pconf.api_key:
|
||||||
|
logger.warning(f"Skipping provider '{provider_name}': no API key")
|
||||||
|
continue
|
||||||
|
models = list(pconf.models.keys()) if pconf.models else []
|
||||||
|
default_model = models[0] if models else "gpt-4o-mini"
|
||||||
|
provider = OpenAICompatibleProvider(
|
||||||
|
api_key=pconf.api_key,
|
||||||
|
base_url=pconf.base_url,
|
||||||
|
default_model=default_model,
|
||||||
|
)
|
||||||
|
gateway.register_provider(provider_name, provider)
|
||||||
|
logger.info(f"Provider '{provider_name}' registered with model '{default_model}'")
|
||||||
|
|
||||||
|
return gateway
|
||||||
|
|
||||||
|
|
||||||
|
def _init_tool_registry() -> ToolRegistry:
|
||||||
|
"""初始化 Tool Registry 并注册 GEO Tools"""
|
||||||
|
registry = ToolRegistry()
|
||||||
|
from configs.geo_tools import register_geo_tools
|
||||||
|
register_geo_tools(registry)
|
||||||
|
return registry
|
||||||
|
|
||||||
|
|
||||||
|
def _init_skill_registry(tool_registry: ToolRegistry) -> SkillRegistry:
|
||||||
|
"""初始化 Skill Registry 并从 configs/skills/ 目录加载"""
|
||||||
|
registry = SkillRegistry()
|
||||||
|
loader = SkillLoader(registry, tool_registry)
|
||||||
|
skills = loader.load_from_directory(SKILLS_DIR)
|
||||||
|
logger.info(f"Loaded {len(skills)} skills from {SKILLS_DIR}")
|
||||||
|
return registry
|
||||||
|
|
||||||
|
|
||||||
|
def create_geo_app() -> "FastAPI":
|
||||||
|
"""GEO AgentKit Server FastAPI 工厂函数"""
|
||||||
|
llm_gateway = _init_llm_gateway()
|
||||||
|
tool_registry = _init_tool_registry()
|
||||||
|
skill_registry = _init_skill_registry(tool_registry)
|
||||||
|
|
||||||
|
app = create_app(
|
||||||
|
llm_gateway=llm_gateway,
|
||||||
|
skill_registry=skill_registry,
|
||||||
|
tool_registry=tool_registry,
|
||||||
|
)
|
||||||
|
app.title = "GEO AgentKit Server"
|
||||||
|
|
||||||
|
logger.info(f"GEO AgentKit Server initialized: {len(skill_registry.list_skills())} skills, "
|
||||||
|
f"{len(tool_registry.list_tools())} tools")
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
@ -0,0 +1,465 @@
|
||||||
|
"""GEO 项目的 Tool 注册 — 供 AgentKit Server 使用
|
||||||
|
|
||||||
|
所有 Tool 通过 HTTP 调用 GEO Backend 的业务 API,不直接 import GEO 服务类。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from agentkit.tools.function_tool import FunctionTool
|
||||||
|
from agentkit.tools.registry import ToolRegistry
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
GEO_BACKEND_URL = os.getenv("GEO_BACKEND_URL", "http://localhost:8000")
|
||||||
|
INTERNAL_API_TOKEN = os.getenv("INTERNAL_API_TOKEN", "")
|
||||||
|
|
||||||
|
|
||||||
|
def _internal_headers() -> dict:
|
||||||
|
"""获取内部 API 请求头"""
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
if INTERNAL_API_TOKEN:
|
||||||
|
headers["X-Internal-Token"] = INTERNAL_API_TOKEN
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Citation Tools ───
|
||||||
|
|
||||||
|
async def execute_single_platform(
|
||||||
|
keyword: str,
|
||||||
|
platform: str,
|
||||||
|
target_brand: str,
|
||||||
|
brand_aliases: list[str] | None = None,
|
||||||
|
) -> dict:
|
||||||
|
"""在单个 AI 平台执行引用检测"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=120.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{GEO_BACKEND_URL}/api/v1/ai-engines/execute-single-platform",
|
||||||
|
json={
|
||||||
|
"keyword": keyword,
|
||||||
|
"platform": platform,
|
||||||
|
"target_brand": target_brand,
|
||||||
|
"brand_aliases": brand_aliases or [],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"execute_single_platform 失败: {e}")
|
||||||
|
return {"error": str(e), "keyword": keyword, "platform": platform}
|
||||||
|
|
||||||
|
|
||||||
|
async def get_or_create_task(query_id: str, platform: str) -> dict:
|
||||||
|
"""获取或创建查询任务 — 通过内部 API"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{GEO_BACKEND_URL}/internal/citation/get-or-create-task",
|
||||||
|
json={"query_id": query_id, "platform": platform},
|
||||||
|
headers=_internal_headers(),
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"get_or_create_task 失败: {e}")
|
||||||
|
return {"error": str(e), "query_id": query_id, "platform": platform}
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Content Tools ───
|
||||||
|
|
||||||
|
async def retrieve_knowledge(
|
||||||
|
knowledge_base_ids: list[str],
|
||||||
|
query: str,
|
||||||
|
top_k: int = 5,
|
||||||
|
) -> dict:
|
||||||
|
"""从知识库检索相关内容 — 通过内部 API"""
|
||||||
|
if not knowledge_base_ids or not query:
|
||||||
|
return {"content": "暂无相关知识库内容", "sources": []}
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{GEO_BACKEND_URL}/internal/knowledge/search",
|
||||||
|
json={"query": query, "knowledge_base_ids": knowledge_base_ids, "top_k": top_k},
|
||||||
|
headers=_internal_headers(),
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
results = data.get("results", [])
|
||||||
|
if results:
|
||||||
|
content_parts = []
|
||||||
|
sources = []
|
||||||
|
for r in results:
|
||||||
|
title = r.get("document_title", "未知")
|
||||||
|
content_parts.append(f"[来源: {title}]\n{r.get('content', '')}")
|
||||||
|
sources.append(title)
|
||||||
|
return {"content": "\n\n---\n\n".join(content_parts), "sources": sources}
|
||||||
|
return {"content": "暂无相关知识库内容", "sources": []}
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"retrieve_knowledge 失败: {e}")
|
||||||
|
return {"content": "暂无相关知识库内容", "sources": []}
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Monitor Tools ───
|
||||||
|
|
||||||
|
async def monitor_check_and_compare(record_id: str) -> dict:
|
||||||
|
"""检测并对比监测记录的变化 — 通过内部 API"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{GEO_BACKEND_URL}/internal/monitor/check",
|
||||||
|
json={"record_id": record_id},
|
||||||
|
headers=_internal_headers(),
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"monitor_check_and_compare 失败: {e}")
|
||||||
|
return {"error": str(e), "record_id": record_id}
|
||||||
|
|
||||||
|
|
||||||
|
async def monitor_generate_report(record_id: str) -> dict:
|
||||||
|
"""生成监测变化报告 — 通过内部 API"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{GEO_BACKEND_URL}/internal/monitor/generate-report",
|
||||||
|
json={"record_id": record_id},
|
||||||
|
headers=_internal_headers(),
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"monitor_generate_report 失败: {e}")
|
||||||
|
return {"error": str(e), "record_id": record_id}
|
||||||
|
|
||||||
|
|
||||||
|
async def monitor_create_record(
|
||||||
|
brand_id: str,
|
||||||
|
query_keywords: str | None = None,
|
||||||
|
platform: str | None = None,
|
||||||
|
check_interval_hours: int = 24,
|
||||||
|
) -> dict:
|
||||||
|
"""创建监测记录 — 通过内部 API"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{GEO_BACKEND_URL}/internal/monitor/create-record",
|
||||||
|
json={
|
||||||
|
"brand_id": brand_id,
|
||||||
|
"query_keywords": query_keywords,
|
||||||
|
"platform": platform,
|
||||||
|
"check_interval_hours": check_interval_hours,
|
||||||
|
},
|
||||||
|
headers=_internal_headers(),
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"monitor_create_record 失败: {e}")
|
||||||
|
return {"error": str(e), "brand_id": brand_id}
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Schema Tools ───
|
||||||
|
|
||||||
|
SCHEMA_TEMPLATES = {
|
||||||
|
"Organization": {
|
||||||
|
"@context": "https://schema.org", "@type": "Organization",
|
||||||
|
"name": "", "description": "", "url": "", "logo": "", "sameAs": [],
|
||||||
|
},
|
||||||
|
"Product": {
|
||||||
|
"@context": "https://schema.org", "@type": "Product",
|
||||||
|
"name": "", "description": "",
|
||||||
|
"brand": {"@type": "Brand", "name": ""},
|
||||||
|
},
|
||||||
|
"FAQPage": {
|
||||||
|
"@context": "https://schema.org", "@type": "FAQPage",
|
||||||
|
"mainEntity": [{"@type": "Question", "name": "", "acceptedAnswer": {"@type": "Answer", "text": ""}}],
|
||||||
|
},
|
||||||
|
"Article": {
|
||||||
|
"@context": "https://schema.org", "@type": "Article",
|
||||||
|
"headline": "", "description": "", "author": {"@type": "Organization", "name": ""},
|
||||||
|
},
|
||||||
|
"LocalBusiness": {
|
||||||
|
"@context": "https://schema.org", "@type": "LocalBusiness",
|
||||||
|
"name": "", "address": {"@type": "PostalAddress"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
DIMENSION_SCHEMA_MAP = {
|
||||||
|
"schema_marketing": ["Organization", "LocalBusiness"],
|
||||||
|
"entity_clarity": ["Organization", "Product"],
|
||||||
|
"citation_readiness": ["FAQPage", "Article"],
|
||||||
|
"brand_visibility": ["Organization", "Product"],
|
||||||
|
"local_seo": ["LocalBusiness"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def fill_schema_with_llm(
|
||||||
|
schema_type: str,
|
||||||
|
brand_info: dict | None = None,
|
||||||
|
diagnosis_dimensions: dict | None = None,
|
||||||
|
) -> dict:
|
||||||
|
"""使用 LLM 填充 Schema JSON-LD 模板 — 通过 GEO Backend 内部 API"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{GEO_BACKEND_URL}/internal/schema/advise",
|
||||||
|
json={
|
||||||
|
"schema_type": schema_type,
|
||||||
|
"brand_info": brand_info or {},
|
||||||
|
"diagnosis_dimensions": diagnosis_dimensions or {},
|
||||||
|
},
|
||||||
|
headers=_internal_headers(),
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"fill_schema_with_llm 失败: {e}")
|
||||||
|
return {"error": str(e), "schema_type": schema_type}
|
||||||
|
|
||||||
|
|
||||||
|
async def identify_missing_dimensions(
|
||||||
|
diagnosis_data: dict,
|
||||||
|
focus_dimensions: list[str] | None = None,
|
||||||
|
) -> dict:
|
||||||
|
"""识别 Schema 缺失维度"""
|
||||||
|
dimensions = []
|
||||||
|
dimension_scores = diagnosis_data.get("dimensions", {})
|
||||||
|
for dim_name, dim_info in dimension_scores.items():
|
||||||
|
if dim_name not in DIMENSION_SCHEMA_MAP:
|
||||||
|
continue
|
||||||
|
if focus_dimensions and dim_name not in focus_dimensions:
|
||||||
|
continue
|
||||||
|
score = dim_info.get("score", 0) if isinstance(dim_info, dict) else dim_info
|
||||||
|
max_score = dim_info.get("max_score", 100) if isinstance(dim_info, dict) else 100
|
||||||
|
percentage = (score / max_score * 100) if max_score > 0 else 0
|
||||||
|
if percentage < 80:
|
||||||
|
dimensions.append({
|
||||||
|
"dimension": dim_name,
|
||||||
|
"current_score": round(score, 2),
|
||||||
|
"max_score": max_score,
|
||||||
|
"percentage": round(percentage, 2),
|
||||||
|
})
|
||||||
|
return {"missing_dimensions": dimensions}
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Competitor Tools ───
|
||||||
|
|
||||||
|
async def competitor_analyze(
|
||||||
|
brand_id: str,
|
||||||
|
analysis_types: list[str] | None = None,
|
||||||
|
period_days: int = 30,
|
||||||
|
) -> dict:
|
||||||
|
"""执行竞品策略分析 — 通过 GEO Backend API"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=120.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{GEO_BACKEND_URL}/api/v1/competitor/analyze",
|
||||||
|
json={
|
||||||
|
"brand_id": brand_id,
|
||||||
|
"analysis_types": analysis_types,
|
||||||
|
"period_days": period_days,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"competitor_analyze 失败: {e}")
|
||||||
|
return {"error": str(e), "brand_id": brand_id}
|
||||||
|
|
||||||
|
|
||||||
|
async def competitor_gap_analysis(
|
||||||
|
brand_id: str,
|
||||||
|
period_days: int = 30,
|
||||||
|
) -> dict:
|
||||||
|
"""执行竞品差距分析 — 通过 GEO Backend API"""
|
||||||
|
return await competitor_analyze(
|
||||||
|
brand_id=brand_id,
|
||||||
|
analysis_types=["citation_gap", "platform_coverage", "query_overlap"],
|
||||||
|
period_days=period_days,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Trend Tools ───
|
||||||
|
|
||||||
|
async def trend_insight(
|
||||||
|
brand_id: str,
|
||||||
|
days: int = 30,
|
||||||
|
platforms: list[str] | None = None,
|
||||||
|
keywords: list[str] | None = None,
|
||||||
|
) -> dict:
|
||||||
|
"""执行趋势洞察分析 — 通过 GEO Backend API"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=120.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{GEO_BACKEND_URL}/api/v1/trends/insight",
|
||||||
|
json={
|
||||||
|
"brand_id": brand_id,
|
||||||
|
"days": days,
|
||||||
|
"platforms": platforms,
|
||||||
|
"keywords": keywords,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"trend_insight 失败: {e}")
|
||||||
|
return {"error": str(e), "brand_id": brand_id}
|
||||||
|
|
||||||
|
|
||||||
|
async def trend_hotspot(
|
||||||
|
brand_id: str,
|
||||||
|
days: int = 30,
|
||||||
|
) -> dict:
|
||||||
|
"""检测引用量突增的热点话题 — 通过 GEO Backend API"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=120.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{GEO_BACKEND_URL}/api/v1/trends/hotspot",
|
||||||
|
json={"brand_id": brand_id, "days": days},
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"trend_hotspot 失败: {e}")
|
||||||
|
return {"error": str(e), "brand_id": brand_id}
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Knowledge Tools ───
|
||||||
|
|
||||||
|
async def search_knowledge(
|
||||||
|
query: str,
|
||||||
|
knowledge_base_ids: list[str],
|
||||||
|
top_k: int = 5,
|
||||||
|
) -> dict:
|
||||||
|
"""从知识库检索相关内容 — 通过内部 API"""
|
||||||
|
return await retrieve_knowledge(
|
||||||
|
knowledge_base_ids=knowledge_base_ids,
|
||||||
|
query=query,
|
||||||
|
top_k=top_k,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def detect_ai_patterns(content: str, platform_id: str) -> dict:
|
||||||
|
"""检测内容中的 AI 生成模式 — 通过 GEO Backend API"""
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{GEO_BACKEND_URL}/api/v1/ai-engines/detect-ai-patterns",
|
||||||
|
json={"content": content, "platform_id": platform_id},
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"detect_ai_patterns 失败: {e}")
|
||||||
|
return {"error": str(e), "patterns": [], "count": 0}
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Registration ───
|
||||||
|
|
||||||
|
def register_geo_tools(registry: ToolRegistry) -> None:
|
||||||
|
"""注册 GEO 项目的所有 Tool"""
|
||||||
|
|
||||||
|
# Citation
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="execute_single_platform",
|
||||||
|
description="在单个AI平台执行引用检测",
|
||||||
|
func=execute_single_platform,
|
||||||
|
tags=["citation", "detection"],
|
||||||
|
))
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="get_or_create_task",
|
||||||
|
description="获取或创建引用检测的查询任务",
|
||||||
|
func=get_or_create_task,
|
||||||
|
tags=["citation", "task"],
|
||||||
|
))
|
||||||
|
|
||||||
|
# Content
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="retrieve_knowledge",
|
||||||
|
description="从知识库检索相关内容",
|
||||||
|
func=retrieve_knowledge,
|
||||||
|
tags=["content", "rag", "knowledge"],
|
||||||
|
))
|
||||||
|
|
||||||
|
# Monitor
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="monitor_check_and_compare",
|
||||||
|
description="检测并对比监测记录的变化",
|
||||||
|
func=monitor_check_and_compare,
|
||||||
|
tags=["monitor", "tracking"],
|
||||||
|
))
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="monitor_generate_report",
|
||||||
|
description="生成监测变化报告",
|
||||||
|
func=monitor_generate_report,
|
||||||
|
tags=["monitor", "report"],
|
||||||
|
))
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="monitor_create_record",
|
||||||
|
description="创建新的监测记录",
|
||||||
|
func=monitor_create_record,
|
||||||
|
tags=["monitor", "record"],
|
||||||
|
))
|
||||||
|
|
||||||
|
# Schema
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="fill_schema_with_llm",
|
||||||
|
description="使用LLM填充Schema JSON-LD模板",
|
||||||
|
func=fill_schema_with_llm,
|
||||||
|
tags=["schema", "llm"],
|
||||||
|
))
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="identify_missing_dimensions",
|
||||||
|
description="识别Schema缺失维度",
|
||||||
|
func=identify_missing_dimensions,
|
||||||
|
tags=["schema", "diagnosis"],
|
||||||
|
))
|
||||||
|
|
||||||
|
# Competitor
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="competitor_analyze",
|
||||||
|
description="执行竞品策略分析",
|
||||||
|
func=competitor_analyze,
|
||||||
|
tags=["competitor", "analysis"],
|
||||||
|
))
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="competitor_gap_analysis",
|
||||||
|
description="执行竞品差距分析",
|
||||||
|
func=competitor_gap_analysis,
|
||||||
|
tags=["competitor", "gap"],
|
||||||
|
))
|
||||||
|
|
||||||
|
# Trend
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="trend_insight",
|
||||||
|
description="分析品牌引用趋势",
|
||||||
|
func=trend_insight,
|
||||||
|
tags=["trend", "insight"],
|
||||||
|
))
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="trend_hotspot",
|
||||||
|
description="检测引用量突增的热点话题",
|
||||||
|
func=trend_hotspot,
|
||||||
|
tags=["trend", "hotspot"],
|
||||||
|
))
|
||||||
|
|
||||||
|
# Knowledge
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="search_knowledge",
|
||||||
|
description="从知识库检索相关内容",
|
||||||
|
func=search_knowledge,
|
||||||
|
tags=["knowledge", "rag"],
|
||||||
|
))
|
||||||
|
registry.register(FunctionTool(
|
||||||
|
name="detect_ai_patterns",
|
||||||
|
description="检测内容中的AI生成模式",
|
||||||
|
func=detect_ai_patterns,
|
||||||
|
tags=["knowledge", "deai"],
|
||||||
|
))
|
||||||
|
|
||||||
|
logger.info(f"GEO tools registered: {len(registry.list_all_tools())} tools")
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# LLM Provider 配置 — AgentKit Server 使用
|
||||||
|
# 环境变量替换:${VAR_NAME} 在启动时由 LLMConfig.from_yaml() 处理
|
||||||
|
|
||||||
|
providers:
|
||||||
|
deepseek:
|
||||||
|
api_key: "${DEEPSEEK_API_KEY}"
|
||||||
|
base_url: "https://api.deepseek.com/v1"
|
||||||
|
models:
|
||||||
|
deepseek-chat:
|
||||||
|
max_tokens: 64000
|
||||||
|
cost_per_1k_input: 0.00014
|
||||||
|
cost_per_1k_output: 0.00028
|
||||||
|
|
||||||
|
openai:
|
||||||
|
api_key: "${OPENAI_API_KEY}"
|
||||||
|
base_url: "${OPENAI_BASE_URL:-https://coding.dashscope.aliyuncs.com/v1}"
|
||||||
|
models:
|
||||||
|
qwen3-coder-plus:
|
||||||
|
max_tokens: 64000
|
||||||
|
cost_per_1k_input: 0.00014
|
||||||
|
cost_per_1k_output: 0.00028
|
||||||
|
|
||||||
|
model_aliases:
|
||||||
|
default: "deepseek/deepseek-chat"
|
||||||
|
fast: "deepseek/deepseek-chat"
|
||||||
|
powerful: "deepseek/deepseek-chat"
|
||||||
|
|
||||||
|
fallbacks:
|
||||||
|
deepseek/deepseek-chat:
|
||||||
|
- "openai/qwen3-coder-plus"
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
name: citation_detector
|
||||||
|
agent_type: citation_detection
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "AI平台引用检测Agent:检测目标品牌在各AI平台回答中的引用情况"
|
||||||
|
task_mode: custom
|
||||||
|
supported_tasks:
|
||||||
|
- citation_detect
|
||||||
|
- citation_detect_single
|
||||||
|
max_concurrency: 3
|
||||||
|
custom_handler: "configs.geo_handlers.handle_citation_task"
|
||||||
|
|
||||||
|
input_schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
query_id:
|
||||||
|
type: string
|
||||||
|
description: 查询ID(citation_detect模式)
|
||||||
|
keyword:
|
||||||
|
type: string
|
||||||
|
description: 关键词(citation_detect_single模式)
|
||||||
|
platform:
|
||||||
|
type: string
|
||||||
|
description: 平台名称(citation_detect_single模式)
|
||||||
|
target_brand:
|
||||||
|
type: string
|
||||||
|
description: 目标品牌(citation_detect_single模式)
|
||||||
|
brand_aliases:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: 品牌别名列表
|
||||||
|
|
||||||
|
output_schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
query_id:
|
||||||
|
type: string
|
||||||
|
keyword:
|
||||||
|
type: string
|
||||||
|
total_records:
|
||||||
|
type: integer
|
||||||
|
cited_count:
|
||||||
|
type: integer
|
||||||
|
records:
|
||||||
|
type: array
|
||||||
|
|
||||||
|
tools:
|
||||||
|
- execute_single_platform
|
||||||
|
- get_or_create_task
|
||||||
|
|
||||||
|
memory:
|
||||||
|
working:
|
||||||
|
enabled: true
|
||||||
|
episodic:
|
||||||
|
enabled: true
|
||||||
|
track_success: true
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
name: competitor_analyzer
|
||||||
|
agent_type: competitor_analysis
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "竞品策略分析Agent:对比品牌与竞品的引用数据,识别差距领域,发现机会点,生成策略建议"
|
||||||
|
task_mode: tool_call
|
||||||
|
supported_tasks:
|
||||||
|
- competitor_analyze
|
||||||
|
- competitor_gap_analysis
|
||||||
|
max_concurrency: 2
|
||||||
|
|
||||||
|
intent:
|
||||||
|
keywords: ["竞品", "对比", "竞争", "competitor", "gap", "分析"]
|
||||||
|
description: "用户需要分析竞品策略、对比品牌差距或发现竞争机会"
|
||||||
|
examples:
|
||||||
|
- "分析我的竞品策略"
|
||||||
|
- "对比我和竞品的差距"
|
||||||
|
- "竞品分析"
|
||||||
|
|
||||||
|
input_schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- brand_id
|
||||||
|
properties:
|
||||||
|
brand_id:
|
||||||
|
type: string
|
||||||
|
description: 品牌ID
|
||||||
|
analysis_types:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: 分析类型列表
|
||||||
|
period_days:
|
||||||
|
type: integer
|
||||||
|
description: 分析周期(天)
|
||||||
|
default: 30
|
||||||
|
|
||||||
|
output_schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
brand_id:
|
||||||
|
type: string
|
||||||
|
analysis:
|
||||||
|
type: object
|
||||||
|
recommendations:
|
||||||
|
type: array
|
||||||
|
|
||||||
|
tools:
|
||||||
|
- competitor_analyze
|
||||||
|
- competitor_gap_analysis
|
||||||
|
|
||||||
|
memory:
|
||||||
|
working:
|
||||||
|
enabled: true
|
||||||
|
episodic:
|
||||||
|
enabled: true
|
||||||
|
track_success: true
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
name: content_generator
|
||||||
|
agent_type: content_generation
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "AI内容生成Agent:支持选题推荐和文章生成,可结合知识库RAG检索"
|
||||||
|
task_mode: llm_generate
|
||||||
|
supported_tasks:
|
||||||
|
- generate_topics
|
||||||
|
- generate_article
|
||||||
|
max_concurrency: 2
|
||||||
|
|
||||||
|
intent:
|
||||||
|
keywords: ["生成内容", "写文章", "选题", "generate", "content", "创作"]
|
||||||
|
description: "用户需要生成SEO/GEO优化内容、推荐选题或撰写文章"
|
||||||
|
examples:
|
||||||
|
- "帮我写一篇关于AI的文章"
|
||||||
|
- "推荐一些选题"
|
||||||
|
- "生成关于品牌的内容"
|
||||||
|
|
||||||
|
input_schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- target_keyword
|
||||||
|
properties:
|
||||||
|
target_keyword:
|
||||||
|
type: string
|
||||||
|
description: 目标关键词
|
||||||
|
brand_name:
|
||||||
|
type: string
|
||||||
|
description: 品牌名称
|
||||||
|
brand_description:
|
||||||
|
type: string
|
||||||
|
description: 品牌描述
|
||||||
|
target_platform:
|
||||||
|
type: string
|
||||||
|
description: 目标平台
|
||||||
|
default: "通用"
|
||||||
|
knowledge_base_ids:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: 知识库ID列表,用于RAG检索
|
||||||
|
topic_title:
|
||||||
|
type: string
|
||||||
|
description: 选题标题(generate_article时使用)
|
||||||
|
word_count:
|
||||||
|
type: integer
|
||||||
|
description: 目标字数
|
||||||
|
default: 2000
|
||||||
|
content_style:
|
||||||
|
type: string
|
||||||
|
description: 内容风格
|
||||||
|
default: "专业严谨"
|
||||||
|
content_angle:
|
||||||
|
type: string
|
||||||
|
description: 内容角度
|
||||||
|
model:
|
||||||
|
type: string
|
||||||
|
description: 指定LLM模型
|
||||||
|
|
||||||
|
output_schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
topics:
|
||||||
|
type: array
|
||||||
|
description: 选题列表
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
description: 生成的文章内容
|
||||||
|
word_count:
|
||||||
|
type: integer
|
||||||
|
usage:
|
||||||
|
type: object
|
||||||
|
|
||||||
|
prompt:
|
||||||
|
identity: "你是一个专业的内容生成助手,擅长为品牌创作高质量的SEO/GEO优化内容"
|
||||||
|
context: "品牌需要通过优质内容提升在AI搜索引擎中的可见性和引用率"
|
||||||
|
instructions: |
|
||||||
|
根据用户提供的关键词、品牌信息和知识库内容,生成符合要求的内容。
|
||||||
|
- generate_topics: 生成选题列表,每个选题包含 title、reason、keywords 字段
|
||||||
|
- generate_article: 生成完整文章,确保内容专业、结构清晰、关键词自然融入
|
||||||
|
constraints: |
|
||||||
|
- 内容必须原创,避免抄袭
|
||||||
|
- 关键词密度适中,不要堆砌
|
||||||
|
- 文章结构清晰,段落分明
|
||||||
|
- 数据和引用需标注来源
|
||||||
|
output_format: "以 JSON 格式输出,generate_topics 返回 {topics: [{title, reason, keywords}]},generate_article 返回 {content, word_count}"
|
||||||
|
examples: ""
|
||||||
|
|
||||||
|
llm:
|
||||||
|
model: "deepseek"
|
||||||
|
temperature: 0.7
|
||||||
|
max_tokens: 4000
|
||||||
|
|
||||||
|
tools:
|
||||||
|
- retrieve_knowledge
|
||||||
|
|
||||||
|
quality_gate:
|
||||||
|
required_fields: ["content"]
|
||||||
|
min_word_count: 500
|
||||||
|
max_retries: 1
|
||||||
|
|
||||||
|
memory:
|
||||||
|
working:
|
||||||
|
enabled: true
|
||||||
|
episodic:
|
||||||
|
enabled: true
|
||||||
|
track_success: true
|
||||||
|
semantic:
|
||||||
|
enabled: true
|
||||||
|
knowledge_base_ids_field: "knowledge_base_ids"
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
name: deai_agent
|
||||||
|
agent_type: deai_processing
|
||||||
|
version: "1.1.0"
|
||||||
|
description: "内容去AI化Agent:消除AI生成特征,使文章更自然流畅"
|
||||||
|
task_mode: llm_generate
|
||||||
|
supported_tasks:
|
||||||
|
- deai_process
|
||||||
|
max_concurrency: 2
|
||||||
|
|
||||||
|
input_schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- content
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
description: 待处理的文章内容
|
||||||
|
platform:
|
||||||
|
type: string
|
||||||
|
description: 目标平台ID(如 zhihu, wechat)
|
||||||
|
style:
|
||||||
|
type: string
|
||||||
|
description: 目标风格
|
||||||
|
default: "自然流畅"
|
||||||
|
preserve_structure:
|
||||||
|
type: boolean
|
||||||
|
description: 是否保留原有结构
|
||||||
|
default: true
|
||||||
|
|
||||||
|
output_schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
description: 处理后的内容
|
||||||
|
original_word_count:
|
||||||
|
type: integer
|
||||||
|
processed_word_count:
|
||||||
|
type: integer
|
||||||
|
usage:
|
||||||
|
type: object
|
||||||
|
detected_ai_patterns:
|
||||||
|
type: array
|
||||||
|
|
||||||
|
prompt:
|
||||||
|
identity: "你是一个专业的内容改写专家,擅长将AI生成的文本改写为自然、人类化的表达"
|
||||||
|
context: "平台对AI生成内容的检测越来越严格,需要将内容改写为更自然的风格"
|
||||||
|
instructions: |
|
||||||
|
对提供的文章内容进行去AI化处理:
|
||||||
|
1. 替换AI常用表达(如"总之"、"综上所述"、"首先其次最后"等)
|
||||||
|
2. 增加口语化表达和个人观点
|
||||||
|
3. 调整句式结构,避免过于工整的排比
|
||||||
|
4. 保留核心信息和数据
|
||||||
|
5. 如有平台特定要求,遵循平台规则
|
||||||
|
constraints: |
|
||||||
|
- 保留原文的核心信息和数据
|
||||||
|
- 不要改变文章的主题和立场
|
||||||
|
- 保持专业性的同时增加自然感
|
||||||
|
- 如指定平台,需符合该平台的内容规范
|
||||||
|
output_format: "返回处理后的完整文章内容"
|
||||||
|
examples: ""
|
||||||
|
|
||||||
|
llm:
|
||||||
|
model: "deepseek"
|
||||||
|
temperature: 0.9
|
||||||
|
max_tokens: 8000
|
||||||
|
|
||||||
|
tools:
|
||||||
|
- detect_ai_patterns
|
||||||
|
|
||||||
|
quality_gate:
|
||||||
|
required_fields: ["content"]
|
||||||
|
min_word_count: 200
|
||||||
|
max_retries: 1
|
||||||
|
|
||||||
|
memory:
|
||||||
|
working:
|
||||||
|
enabled: true
|
||||||
|
episodic:
|
||||||
|
enabled: true
|
||||||
|
track_success: true
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
name: geo_optimizer
|
||||||
|
agent_type: geo_optimization
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "GEO/SEO内容优化Agent:提升内容在AI搜索引擎中的可见性和引用率"
|
||||||
|
task_mode: llm_generate
|
||||||
|
supported_tasks:
|
||||||
|
- geo_optimize
|
||||||
|
max_concurrency: 2
|
||||||
|
|
||||||
|
input_schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- content
|
||||||
|
- target_keywords
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
description: 待优化文章
|
||||||
|
target_keywords:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: 目标关键词列表
|
||||||
|
target_platform:
|
||||||
|
type: string
|
||||||
|
description: 目标平台
|
||||||
|
default: "通用"
|
||||||
|
optimization_level:
|
||||||
|
type: string
|
||||||
|
enum: [light, moderate, aggressive]
|
||||||
|
description: 优化级别
|
||||||
|
default: "moderate"
|
||||||
|
|
||||||
|
output_schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
optimized_content:
|
||||||
|
type: string
|
||||||
|
seo_score:
|
||||||
|
type: number
|
||||||
|
changes:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
usage:
|
||||||
|
type: object
|
||||||
|
|
||||||
|
prompt:
|
||||||
|
identity: "你是一个GEO/SEO优化专家,擅长优化内容以提升在AI搜索引擎中的可见性"
|
||||||
|
context: "品牌需要通过内容优化提升在AI搜索结果中的引用率和排名"
|
||||||
|
instructions: |
|
||||||
|
对提供的文章进行GEO/SEO优化:
|
||||||
|
1. 自然融入目标关键词
|
||||||
|
2. 优化标题和段落结构
|
||||||
|
3. 增加结构化数据标记建议
|
||||||
|
4. 提升内容的权威性和引用价值
|
||||||
|
5. 根据optimization_level调整优化力度
|
||||||
|
constraints: |
|
||||||
|
- 优化后的内容必须保持原意
|
||||||
|
- 关键词融入要自然,避免堆砌
|
||||||
|
- 保持文章可读性
|
||||||
|
- 不要添加虚假信息
|
||||||
|
output_format: "以 JSON 格式输出: {optimized_content: string, seo_score: number, changes: [string]}"
|
||||||
|
examples: ""
|
||||||
|
|
||||||
|
llm:
|
||||||
|
model: "deepseek"
|
||||||
|
temperature: 0.5
|
||||||
|
max_tokens: 8000
|
||||||
|
|
||||||
|
tools: []
|
||||||
|
|
||||||
|
quality_gate:
|
||||||
|
required_fields: ["optimized_content"]
|
||||||
|
min_word_count: 200
|
||||||
|
max_retries: 1
|
||||||
|
|
||||||
|
memory:
|
||||||
|
working:
|
||||||
|
enabled: true
|
||||||
|
episodic:
|
||||||
|
enabled: true
|
||||||
|
track_success: true
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
name: monitor
|
||||||
|
agent_type: performance_tracker
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "效果追踪Agent:监测品牌引用量、情感、排名变化,生成变化报告"
|
||||||
|
task_mode: custom
|
||||||
|
supported_tasks:
|
||||||
|
- monitor_track
|
||||||
|
- monitor_check_single
|
||||||
|
max_concurrency: 3
|
||||||
|
custom_handler: "configs.geo_handlers.handle_monitor_task"
|
||||||
|
|
||||||
|
input_schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- brand_id
|
||||||
|
properties:
|
||||||
|
brand_id:
|
||||||
|
type: string
|
||||||
|
description: 品牌ID
|
||||||
|
keyword:
|
||||||
|
type: string
|
||||||
|
description: 关键词(monitor_check_single模式)
|
||||||
|
platform:
|
||||||
|
type: string
|
||||||
|
description: 平台名称(monitor_check_single模式)
|
||||||
|
check_interval_hours:
|
||||||
|
type: integer
|
||||||
|
description: 检测间隔小时数
|
||||||
|
default: 24
|
||||||
|
|
||||||
|
output_schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
brand_id:
|
||||||
|
type: string
|
||||||
|
brand_name:
|
||||||
|
type: string
|
||||||
|
total_queries:
|
||||||
|
type: integer
|
||||||
|
checked_records:
|
||||||
|
type: integer
|
||||||
|
reports:
|
||||||
|
type: array
|
||||||
|
|
||||||
|
tools:
|
||||||
|
- monitor_check_and_compare
|
||||||
|
- monitor_generate_report
|
||||||
|
- monitor_create_record
|
||||||
|
|
||||||
|
memory:
|
||||||
|
working:
|
||||||
|
enabled: true
|
||||||
|
episodic:
|
||||||
|
enabled: true
|
||||||
|
track_success: true
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
name: schema_advisor
|
||||||
|
agent_type: schema_advisor
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Schema优化建议Agent:识别Schema缺失维度,生成JSON-LD结构化数据建议"
|
||||||
|
task_mode: custom
|
||||||
|
supported_tasks:
|
||||||
|
- schema_advise
|
||||||
|
max_concurrency: 2
|
||||||
|
custom_handler: "configs.geo_handlers.handle_schema_task"
|
||||||
|
|
||||||
|
input_schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- brand_id
|
||||||
|
properties:
|
||||||
|
brand_id:
|
||||||
|
type: string
|
||||||
|
description: 品牌ID
|
||||||
|
diagnosis_data:
|
||||||
|
type: object
|
||||||
|
description: 诊断数据
|
||||||
|
brand_info:
|
||||||
|
type: object
|
||||||
|
description: 品牌信息
|
||||||
|
focus_dimensions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: 重点关注维度
|
||||||
|
|
||||||
|
output_schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
brand_id:
|
||||||
|
type: string
|
||||||
|
suggestions:
|
||||||
|
type: array
|
||||||
|
total:
|
||||||
|
type: integer
|
||||||
|
|
||||||
|
tools:
|
||||||
|
- fill_schema_with_llm
|
||||||
|
|
||||||
|
memory:
|
||||||
|
working:
|
||||||
|
enabled: true
|
||||||
|
episodic:
|
||||||
|
enabled: true
|
||||||
|
track_success: true
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
name: trend_agent
|
||||||
|
agent_type: trend_analysis
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "趋势洞察Agent:分析品牌引用趋势、识别热点话题、推断变化原因并生成建议"
|
||||||
|
task_mode: tool_call
|
||||||
|
supported_tasks:
|
||||||
|
- trend_insight
|
||||||
|
- trend_hotspot
|
||||||
|
max_concurrency: 2
|
||||||
|
|
||||||
|
intent:
|
||||||
|
keywords: ["趋势", "热点", "洞察", "trend", "hotspot", "insight"]
|
||||||
|
description: "用户需要分析品牌趋势、识别热点话题或获取行业洞察"
|
||||||
|
examples:
|
||||||
|
- "分析品牌趋势"
|
||||||
|
- "最近的热点话题是什么"
|
||||||
|
- "趋势洞察"
|
||||||
|
|
||||||
|
input_schema:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- brand_id
|
||||||
|
properties:
|
||||||
|
brand_id:
|
||||||
|
type: string
|
||||||
|
description: 品牌ID
|
||||||
|
days:
|
||||||
|
type: integer
|
||||||
|
description: 分析天数
|
||||||
|
default: 30
|
||||||
|
platforms:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: 平台列表
|
||||||
|
keywords:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: 关键词列表
|
||||||
|
|
||||||
|
output_schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
brand_id:
|
||||||
|
type: string
|
||||||
|
trends:
|
||||||
|
type: array
|
||||||
|
hotspots:
|
||||||
|
type: array
|
||||||
|
|
||||||
|
tools:
|
||||||
|
- trend_insight
|
||||||
|
- trend_hotspot
|
||||||
|
|
||||||
|
memory:
|
||||||
|
working:
|
||||||
|
enabled: true
|
||||||
|
episodic:
|
||||||
|
enabled: true
|
||||||
|
track_success: true
|
||||||
Loading…
Reference in New Issue