# GEO 项目迁移至 AgentKit v2 Mode A 方案

## 1. 目标

将 GEO 项目从当前的**旧框架 + import 混合模式**迁移至 **AgentKit v2 Mode A（HTTP API 模式）**。

迁移完成后：
- AgentKit Server 独立部署，GEO 通过 HTTP API 调用
- LLM 调用统一由 AgentKit Server 的 LLM Gateway 管理
- 意图识别、ReAct 循环、质量检查、标准化输出全部在 AgentKit Server 内完成
- GEO 项目不再直接 import agentkit 内部类

## 2. 当前架构 vs 目标架构

### 当前架构（3 条调用链并存）

```
┌─────────────────────────────────────────────────────────┐
│                     GEO Backend                          │
│                                                         │
│  Chain A: API Route → TaskDispatcher → Redis → BaseAgent │
│  Chain B: Service → 直接实例化 Agent → 直接调用 execute() │
│  Chain C: Adapter → ConfigDrivenAgent → custom_handler   │
│                                                         │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  GEO 内部的旧框架（BaseAgent + Redis Queue + DB）    │ │
│  │  + agentkit import（ConfigDrivenAgent + ToolRegistry）│ │
│  │  + LLMFactory（GEO 自己的 LLM 封装）                 │ │
│  └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```

### 目标架构（Mode A）

```
┌──────────────────────┐     HTTP API      ┌──────────────────────────┐
│    GEO Backend       │ ───────────────→  │   AgentKit Server        │
│                      │                   │                          │
│  API Routes          │  POST /tasks      │  Intent Router           │
│  Services            │  GET /tasks/{id}  │  ReAct Engine            │
│  Workers             │  GET /llm/usage   │  LLM Gateway             │
│                      │                   │  Quality Gate            │
│  不再 import         │                   │  Output Standardizer     │
│  agentkit 内部类     │                   │  AgentPool               │
│                      │                   │  SkillRegistry           │
│  只用 AgentKitClient │                   │  ToolRegistry            │
│                      │                   │  MCP Bridge              │
└──────────────────────┘                   └──────────────────────────┘
                                                    │
                                              ┌─────┴─────┐
                                              │  LLM APIs  │
                                              └───────────┘
```

## 3. 需要改动的文件清单

### 3.1 必须改动（核心迁移）

| 文件 | 当前用法 | 改动内容 |
|------|---------|---------|
| `app/agent_framework/adapter.py` | import agentkit 内部类 | 改为只提供 `get_agentkit_client()` 和 `submit_task_via_api()` |
| `app/agent_framework/__init__.py` | 导出大量 agentkit 类 | 精简导出，只暴露 `AgentKitClient` 相关 |
| `app/api/agents.py` | 用旧 `TaskDispatcher` + `TaskMessage` | 改为调用 `AgentKitClient.submit_task()` |
| `app/services/content/content_generation_service.py` | 用旧 `TaskDispatcher` + 轮询 | 改为调用 `AgentKitClient.submit_task()` |
| `app/services/citation/citation.py` | 直接实例化 `CitationDetectorAgent` | 改为调用 `AgentKitClient.submit_task()` |
| `app/workers/scheduler.py` | 直接实例化 `CitationDetectorAgent` | 改为调用 `AgentKitClient.submit_task()` |

### 3.2 需要迁移到 AgentKit Server 的代码

| 当前位置 | 功能 | 迁移目标 |
|---------|------|---------|
| `app/agent_framework/agents/custom_handlers/citation_handler.py` | 引用检测业务逻辑 | AgentKit Server 的 Tool 或 custom_handler |
| `app/agent_framework/agents/custom_handlers/monitor_handler.py` | 监控业务逻辑 | AgentKit Server 的 Tool 或 custom_handler |
| `app/agent_framework/agents/custom_handlers/schema_handler.py` | Schema 建议业务逻辑 | AgentKit Server 的 Tool 或 custom_handler |
| `app/agent_framework/tools/*.py`（14 个 FunctionTool） | 业务 Tool 定义 | AgentKit Server 的 ToolRegistry |
| `app/agent_framework/agents/configs/*.yaml`（8 个） | Agent 配置 | AgentKit Server 的 SkillLoader 加载目录 |

### 3.3 可删除（迁移完成后）

| 文件/目录 | 原因 |
|----------|------|
| `app/agent_framework/base.py` | 旧 BaseAgent，被 AgentKit Server 取代 |
| `app/agent_framework/dispatcher.py` | 旧 TaskDispatcher，被 AgentKit Server 取代 |
| `app/agent_framework/registry.py` | 旧 AgentRegistry，被 AgentKit Server 取代 |
| `app/agent_framework/protocol.py` | 旧协议类，被 agentkit.core.protocol 取代 |
| `app/agent_framework/exceptions.py` | 旧异常类，被 agentkit.core.exceptions 取代 |
| `app/agent_framework/config_manager.py` | 旧配置管理，被 SkillConfig 取代 |
| `app/agent_framework/standalone.py` | 旧运行器，被 AgentKit Server 取代 |
| `app/agent_framework/pipeline/` | 旧 Pipeline，被 AgentKit Server 编排取代 |
| `app/agent_framework/agents/` 下的旧 Agent 类 | 被 YAML 配置 + Skill 取代 |

## 4. 分步迁移方案

### Phase 1：部署 AgentKit Server + 配置迁移

**目标**：AgentKit Server 能独立运行，加载 GEO 的 8 个 Skill 配置和 14 个 Tool。

#### 4.1.1 创建 AgentKit Server 启动配置

在 `fischer-agentkit/` 项目中创建：

```yaml
# configs/llm_config.yaml — LLM Provider 配置
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

model_aliases:
  default: "deepseek-chat"
  fast: "deepseek-chat"
  powerful: "deepseek-chat"

fallbacks:
  deepseek-chat: []
```

#### 4.1.2 迁移 YAML 配置为 SkillConfig

现有 8 个 YAML 无需修改即可加载（SkillConfig 向后兼容 AgentConfig）。
但建议为需要意图识别的 Skill 添加 `intent` 字段：

```yaml
# content_generator.yaml — 增加的 v2 字段
intent:
  keywords: ["生成内容", "写文章", "选题", "generate", "content"]
  description: "用户需要生成SEO/GEO优化内容、推荐选题或撰写文章"
  examples:
    - "帮我写一篇关于AI的文章"
    - "推荐一些选题"

execution_mode: react  # 使用 ReAct 引擎
max_steps: 5

quality_gate:
  required_fields: ["content"]
  min_word_count: 500
  max_retries: 1
```

#### 4.1.3 迁移 14 个 FunctionTool 到 AgentKit Server

将 GEO 的 Tool 注册代码迁移为 AgentKit Server 的 Tool 插件。

**方式 A（推荐）**：在 AgentKit Server 启动时注册 Tool

```python
# fischer-agentkit/configs/geo_tools.py
"""GEO 项目的 Tool 注册 — 供 AgentKit Server 使用"""

from agentkit.tools.function_tool import FunctionTool
from agentkit.tools.registry import ToolRegistry


def register_geo_tools(registry: ToolRegistry) -> None:
    """注册 GEO 项目的所有 Tool"""

    # --- Citation Tools ---
    async def execute_single_platform(keyword: str, platform: str,
                                       target_brand: str, brand_aliases: list[str] = None):
        """在单个 AI 平台执行引用检测"""
        # 调用 GEO 的业务服务（通过 HTTP 调用 GEO Backend API）
        from agentkit.tools.function_tool import FunctionTool
        # ... 实现 ...

    registry.register(FunctionTool(
        name="execute_single_platform",
        description="在单个AI平台执行引用检测",
        func=execute_single_platform,
        input_schema={...},
        tags=["citation", "detection"],
    ))
    # ... 注册其他 13 个 Tool ...
```

**方式 B**：custom_handler 保持为 custom 模式

3 个 custom_handler（citation/monitor/schema）因为涉及复杂的 DB 操作和多服务编排，
可以保持 `execution_mode: custom`，在 AgentKit Server 中注册为 custom_handler。

```python
# fischer-agentkit/configs/geo_handlers.py
"""GEO 项目的 Custom Handler — 供 AgentKit Server 使用"""

async def handle_citation_task(task):
    """引用检测 handler — 通过 HTTP 调用 GEO Backend 的业务 API"""
    import httpx
    async with httpx.AsyncClient() as client:
        if task.task_type == "citation_detect":
            resp = await client.post(
                "http://geo-backend:8000/internal/citation/detect",
                json=task.input_data,
            )
            return resp.json()
        elif task.task_type == "citation_detect_single":
            resp = await client.post(
                "http://geo-backend:8000/internal/citation/detect-single",
                json=task.input_data,
            )
            return resp.json()
```

> **关键决策**：custom_handler 需要 DB 访问。有两种方案：
> - **方案 1（推荐）**：AgentKit Server 通过 HTTP 回调 GEO Backend 的内部 API 访问 DB
> - **方案 2**：AgentKit Server 直接连接 GEO 的数据库（耦合度高，不推荐）

#### 4.1.4 创建 AgentKit Server 启动脚本

```python
# fischer-agentkit/configs/geo_server.py
"""GEO 专用 AgentKit Server 启动配置"""

from agentkit.server.app import create_app
from agentkit.llm.gateway import LLMGateway
from agentkit.llm.config import LLMConfig
from agentkit.skills.loader import SkillLoader
from agentkit.skills.registry import SkillRegistry
from agentkit.tools.registry import ToolRegistry

from configs.geo_tools import register_geo_tools
from configs.geo_handlers import handle_citation_task, handle_monitor_task, handle_schema_task


def create_geo_app():
    # 1. 初始化 LLM Gateway
    llm_config = LLMConfig.from_yaml("configs/llm_config.yaml")
    llm_gateway = LLMGateway(config=llm_config)

    # 2. 初始化 Tool Registry
    tool_registry = ToolRegistry()
    register_geo_tools(tool_registry)

    # 3. 初始化 Skill Registry
    skill_registry = SkillRegistry()
    loader = SkillLoader(skill_registry=skill_registry, tool_registry=tool_registry)
    loader.load_from_directory("configs/skills")  # 8 个 YAML

    # 4. 创建 FastAPI App
    app = create_app(
        llm_gateway=llm_gateway,
        skill_registry=skill_registry,
        tool_registry=tool_registry,
    )

    return app


# 启动命令：
# uvicorn configs.geo_server:create_geo_app --factory --host 0.0.0.0 --port 8000
```

### Phase 2：GEO Backend 改造

**目标**：GEO Backend 不再直接使用 agentkit 内部类，全部通过 `AgentKitClient` 调用。

#### 4.2.1 改造 adapter.py

```python
# app/agent_framework/adapter.py — Mode A 版本
"""GEO Agent 适配层 — Mode A（HTTP API）

所有 Agent 操作通过 AgentKit Server 的 HTTP API 完成。
GEO Backend 不再 import agentkit 内部类。
"""

import logging
import os

from agentkit.server.client import AgentKitClient

logger = logging.getLogger(__name__)

_AGENTKIT_CLIENT: AgentKitClient | None = None


def get_agentkit_client() -> AgentKitClient:
    """获取 AgentKit Server HTTP 客户端

    环境变量：
        AGENTKIT_SERVER_URL: AgentKit Server 地址，默认 http://localhost:8000
    """
    global _AGENTKIT_CLIENT
    if _AGENTKIT_CLIENT is None:
        base_url = os.getenv("AGENTKIT_SERVER_URL", "http://localhost:8000")
        _AGENTKIT_CLIENT = AgentKitClient(base_url=base_url)
        logger.info(f"AgentKitClient initialized: {base_url}")
    return _AGENTKIT_CLIENT


async def submit_task(
    input_data: dict,
    skill_name: str | None = None,
    agent_name: str | None = None,
) -> dict:
    """提交任务到 AgentKit Server

    Args:
        input_data: 任务输入数据
        skill_name: 指定 Skill 名称（可选，不指定则自动路由）
        agent_name: 指定 Agent 名称（可选）

    Returns:
        标准化输出结果，包含 skill_name, data, metadata
    """
    client = get_agentkit_client()
    result = await client.submit_task(
        input_data=input_data,
        skill_name=skill_name,
        agent_name=agent_name,
    )
    return result


async def get_task_status(task_id: str) -> dict:
    """查询任务状态"""
    client = get_agentkit_client()
    return await client.get_task_status(task_id)


async def get_llm_usage(agent_name: str | None = None) -> dict:
    """查询 LLM 用量统计"""
    client = get_agentkit_client()
    return await client.get_usage(agent_name=agent_name)
```

#### 4.2.2 改造 API 路由（app/api/agents.py）

```python
# 改造前：
from app.agent_framework.dispatcher import TaskDispatcher
from app.agent_framework.protocol import TaskMessage, TaskStatus

task = TaskMessage(...)
dispatcher = TaskDispatcher(settings.REDIS_URL)
await dispatcher.dispatch(task, ...)

# 改造后：
from app.agent_framework.adapter import submit_task, get_task_status, get_llm_usage

result = await submit_task(
    input_data=body.input_data,
    skill_name=body.agent_name,  # agent_name 映射为 skill_name
)
```

#### 4.2.3 改造 ContentGenerationService

```python
# 改造前（三阶段轮询）：
from app.agent_framework.dispatcher import TaskDispatcher
from app.agent_framework.protocol import TaskMessage

dispatcher = TaskDispatcher(settings.REDIS_URL)
task = TaskMessage(agent_name="content_generator", ...)
dispatched_id = await dispatcher.dispatch(task, ...)
result = await self._poll_task_result(dispatcher, dispatched_id, timeout=300)

# 改造后（单次调用，AgentKit Server 内部编排）：
from app.agent_framework.adapter import submit_task

result = await submit_task(
    input_data={
        "target_keyword": keyword,
        "brand_name": brand_name,
        "target_platform": platform,
        "word_count": word_count,
        "content_style": content_style,
        "run_deai": run_deai,
        "run_geo": run_geo,
    },
    skill_name="content_generator",
)
content = result["data"]["content"]
```

> **注意**：当前 content_generation_service 的三阶段（generate → de-AI → GEO optimize）
> 是通过 3 次独立的 TaskDispatcher.dispatch 实现的。
> 迁移到 Mode A 后，有两种方案：
>
> **方案 1（推荐）**：在 AgentKit Server 中创建一个 `content_production` Pipeline Skill，
> 内部编排 3 个子 Skill 的执行顺序。GEO 只需一次 `submit_task` 调用。
>
> **方案 2（简单）**：GEO 仍然调用 3 次 `submit_task`，每次指定不同的 skill_name。
> 改动最小，但调用方仍需编排逻辑。

#### 4.2.4 改造 Citation 和 Scheduler

```python
# 改造前（直接实例化）：
from app.agent_framework.agents import CitationDetectorAgent
agent = CitationDetectorAgent()
result = await agent.execute(task)

# 改造后：
from app.agent_framework.adapter import submit_task
result = await submit_task(
    input_data={"keyword": keyword, "platform": platform, ...},
    skill_name="citation_detector",
)
```

### Phase 3：GEO Backend 内部 API（供 AgentKit Server 回调）

custom_handler 需要 DB 访问，AgentKit Server 通过 HTTP 回调 GEO Backend。

#### 4.3.1 新增内部 API 路由

```python
# app/api/internal.py — 仅供 AgentKit Server 内部调用
"""内部 API — 供 AgentKit Server 回调访问 GEO 业务逻辑"""

from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import get_db

router = APIRouter(prefix="/internal", tags=["internal"])


@router.post("/citation/detect")
async def citation_detect(input_data: dict, db: AsyncSession = Depends(get_db)):
    """引用检测 — 供 AgentKit Server 的 citation_handler 回调"""
    from app.services.citation.citation import CitationService
    service = CitationService()
    return await service.detect_full(input_data, db=db)


@router.post("/citation/detect-single")
async def citation_detect_single(input_data: dict, db: AsyncSession = Depends(get_db)):
    """单平台引用检测 — 供 AgentKit Server 回调"""
    from app.services.citation.citation import CitationService
    service = CitationService()
    return await service.detect_single(input_data, db=db)


@router.post("/monitor/check")
async def monitor_check(input_data: dict, db: AsyncSession = Depends(get_db)):
    """品牌监控检查 — 供 AgentKit Server 的 monitor_handler 回调"""
    from app.services.monitor.monitor_service import MonitorService
    service = MonitorService()
    return await service.check_and_compare(input_data, db=db)


@router.post("/schema/advise")
async def schema_advise(input_data: dict, db: AsyncSession = Depends(get_db)):
    """Schema 建议 — 供 AgentKit Server 的 schema_handler 回调"""
    from app.services.schema.schema_service import SchemaService
    service = SchemaService()
    return await service.advise(input_data, db=db)


@router.post("/knowledge/search")
async def knowledge_search(input_data: dict, db: AsyncSession = Depends(get_db)):
    """知识库检索 — 供 AgentKit Server 的 retrieve_knowledge Tool 回调"""
    from app.services.knowledge.rag_service import RAGService
    service = RAGService()
    results = await service.search(
        session=db,
        query=input_data["query"],
        knowledge_base_ids=input_data.get("knowledge_base_ids", []),
        top_k=input_data.get("top_k", 3),
    )
    return {"results": results}
```

> **安全**：内部 API 应限制只允许 AgentKit Server 的 IP 访问，或使用内部认证 Token。

### Phase 4：清理旧代码

迁移完成并验证后，删除以下文件/目录：

```
app/agent_framework/
├── base.py              # 删除
├── dispatcher.py        # 删除
├── registry.py          # 删除
├── protocol.py          # 删除
├── exceptions.py        # 删除
├── config_manager.py    # 删除
├── standalone.py        # 删除
├── pipeline/            # 删除
└── agents/
    ├── __init__.py      # 删除（旧 Agent 类导出）
    ├── base_agent.py    # 删除
    ├── citation_detector.py  # 删除
    ├── ...其他旧 Agent 类    # 删除
    └── configs/         # 保留（已迁移到 AgentKit Server）
```

保留的文件：
```
app/agent_framework/
├── __init__.py          # 精简，只导出 AgentKitClient 相关
├── adapter.py           # Mode A 版本
└── tools/               # 保留（Tool 定义已迁移到 AgentKit Server，但可作为参考）
```

## 5. 部署架构

### 5.1 docker-compose 配置

```yaml
# docker-compose.yml
version: "3.8"

services:
  # GEO Backend
  geo-backend:
    build: ./geo/backend
    ports:
      - "8000:8000"
    environment:
      - AGENTKIT_SERVER_URL=http://agentkit-server:8001
      - DATABASE_URL=postgresql+asyncpg://...
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - agentkit-server
      - postgres
      - redis

  # AgentKit Server
  agentkit-server:
    build: ./fischer-agentkit
    command: uvicorn configs.geo_server:create_geo_app --factory --host 0.0.0.0 --port 8001
    ports:
      - "8001:8001"
    environment:
      - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - GEO_BACKEND_URL=http://geo-backend:8000
    volumes:
      - ./fischer-agentkit/configs:/app/configs
    depends_on:
      - postgres
      - redis

  postgres:
    image: pgvector/pg15:latest
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
```

### 5.2 网络拓扑

```
                    ┌──────────────┐
                    │   Frontend   │
                    └──────┬───────┘
                           │
                    ┌──────▼───────┐
                    │ GEO Backend  │ :8000
                    │  (FastAPI)   │
                    └──────┬───────┘
                           │ HTTP
                    ┌──────▼───────┐
                    │ AgentKit Svr │ :8001
                    │  (FastAPI)   │
                    └──────┬───────┘
                      ┌────┼────┐
                      │    │    │
                 ┌────▼┐ ┌▼───┐ ┌▼────┐
                 │Redis│ │ PG │ │ LLM │
                 └─────┘ └────┘ └─────┘

AgentKit Server ←→ GEO Backend：内部 API 回调（custom_handler 访问 DB）
GEO Backend ←→ AgentKit Server：HTTP API（submit_task / get_usage）
```

## 6. 迁移检查清单

### Phase 1：AgentKit Server 部署
- [ ] 创建 `configs/llm_config.yaml`
- [ ] 将 8 个 YAML 配置复制到 `configs/skills/` 目录
- [ ] 为需要意图识别的 Skill 添加 `intent` 字段
- [ ] 迁移 14 个 FunctionTool 到 `configs/geo_tools.py`
- [ ] 迁移 3 个 custom_handler 到 `configs/geo_handlers.py`
- [ ] 创建 `configs/geo_server.py` 启动配置
- [ ] 验证 AgentKit Server 能独立启动并加载所有 Skill/Tool
- [ ] 验证 `POST /api/v1/health` 返回 ok

### Phase 2：GEO Backend 改造
- [ ] 改造 `adapter.py` 为 Mode A 版本
- [ ] 改造 `app/api/agents.py` 使用 `submit_task()`
- [ ] 改造 `content_generation_service.py` 使用 `submit_task()`
- [ ] 改造 `citation.py` 和 `scheduler.py` 使用 `submit_task()`
- [ ] 新增 `app/api/internal.py` 内部 API
- [ ] 配置 `AGENTKIT_SERVER_URL` 环境变量
- [ ] 端到端测试：提交任务 → AgentKit 处理 → 返回结果

### Phase 3：清理
- [ ] 删除旧框架文件（base.py, dispatcher.py, registry.py 等）
- [ ] 删除旧 Agent 类文件
- [ ] 更新 `__init__.py` 导出
- [ ] 全量回归测试

## 7. 风险与缓解

| 风险 | 影响 | 缓解 |
|------|------|------|
| custom_handler 需要回调 GEO Backend | 增加网络延迟和故障点 | 内部 API 加超时+重试；AgentKit Server 和 GEO Backend 部署在同一网络 |
| 三阶段内容生成编排 | 调用方式变化 | 推荐 Pipeline Skill 方案，一次调用完成三阶段 |
| 旧代码删除导致其他模块 break | 运行时错误 | 逐文件删除，每次删除后跑全量测试 |
| AgentKit Server 单点故障 | 所有 Agent 功能不可用 | 部署多实例 + 负载均衡 |
| LLM API Key 安全 | 泄露风险 | AgentKit Server 环境变量注入，不写入代码或配置文件 |
