# Kimi平台集成

<cite>
**本文档引用的文件**
- [backend/app/workers/platforms/kimi.py](file://backend/app/workers/platforms/kimi.py)
- [backend/app/workers/platforms/base.py](file://backend/app/workers/platforms/base.py)
- [backend/app/workers/platforms/search_engine.py](file://backend/app/workers/platforms/search_engine.py)
- [backend/app/workers/platforms/__init__.py](file://backend/app/workers/platforms/__init__.py)
- [backend/app/workers/citation_engine.py](file://backend/app/workers/citation_engine.py)
- [backend/app/workers/scheduler.py](file://backend/app/workers/scheduler.py)
- [backend/app/models/query.py](file://backend/app/workers/models/query.py)
- [backend/app/models/citation_record.py](file://backend/app/models/citation_record.py)
- [backend/app/models/query_task.py](file://backend/app/models/query_task.py)
- [backend/app/services/query.py](file://backend/app/services/query.py)
- [backend/app/services/citation.py](file://backend/app/services/citation.py)
- [backend/app/api/queries.py](file://backend/app/api/queries.py)
- [backend/app/api/citations.py](file://backend/app/api/citations.py)
- [backend/app/main.py](file://backend/app/main.py)
- [frontend/lib/platforms.ts](file://frontend/lib/platforms.ts)
</cite>

## 更新摘要
**变更内容**
- Kimi平台适配器已简化为搜索引擎模式，移除了Playwright浏览器自动化实现
- 所有平台适配器采用统一的fetch_search_content机制
- 引入了通用搜索引擎模块，支持DuckDuckGo和Wikipedia回退机制
- 更新了架构图和组件分析以反映新的实现方式

## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考虑](#性能考虑)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)

## 简介
本文件面向需要集成Kimi平台的开发者与运维人员，系统性阐述Kimi适配器的简化实现方式与运行机制。Kimi适配器现已采用搜索引擎查询机制，通过DuckDuckGo和Wikipedia获取与关键词相关的真实内容，替代了原有的Playwright浏览器自动化方案。文档涵盖搜索引擎集成、错误重试与超时处理、资源管理与API调用示例。

## 项目结构
后端采用FastAPI + SQLAlchemy + APScheduler的异步架构，前端通过Next.js提供可视化界面。Kimi适配器位于工作线程模块，被引用检测引擎统一编排，定时调度器周期性触发查询任务。所有平台适配器现在共享相同的搜索引擎基础设施。

```mermaid
graph TB
subgraph "前端"
FE_Platforms["前端平台映射<br/>frontend/lib/platforms.ts"]
end
subgraph "后端"
API_Queries["查询API<br/>backend/app/api/queries.py"]
API_Citations["引用API<br/>backend/app/api/citations.py"]
Service_Query["查询服务<br/>backend/app/services/query.py"]
Service_Citation["引用服务<br/>backend/app/services/citation.py"]
Scheduler["定时调度器<br/>backend/app/workers/scheduler.py"]
Engine["引用检测引擎<br/>backend/app/workers/citation_engine.py"]
Adapter_Kimi["Kimi适配器<br/>backend/app/workers/platforms/kimi.py"]
SearchEngine["搜索引擎模块<br/>backend/app/workers/platforms/search_engine.py"]
BaseAdapter["基础适配器<br/>backend/app/workers/platforms/base.py"]
Model_Query["查询模型<br/>backend/app/models/query.py"]
Model_Record["引用记录模型<br/>backend/app/models/citation_record.py"]
Model_Task["查询任务模型<br/>backend/app/models/query_task.py"]
end
FE_Platforms --> API_Queries
FE_Platforms --> API_Citations
API_Queries --> Service_Query
API_Citations --> Service_Citation
Service_Query --> Scheduler
Service_Citation --> Scheduler
Scheduler --> Engine
Engine --> Adapter_Kimi
Engine --> SearchEngine
Engine --> BaseAdapter
Engine --> Model_Query
Engine --> Model_Record
Engine --> Model_Task
```

**图表来源**
- [backend/app/main.py:13-22](file://backend/app/main.py#L13-L22)
- [backend/app/workers/scheduler.py:25-40](file://backend/app/workers/scheduler.py#L25-L40)
- [backend/app/workers/citation_engine.py:164-173](file://backend/app/workers/citation_engine.py#L164-L173)
- [backend/app/workers/platforms/kimi.py:10-37](file://backend/app/workers/platforms/kimi.py#L10-L37)
- [backend/app/workers/platforms/search_engine.py:1-174](file://backend/app/workers/platforms/search_engine.py#L1-L174)
- [backend/app/models/query.py:11-55](file://backend/app/models/query.py#L11-L55)
- [backend/app/models/citation_record.py:11-42](file://backend/app/models/citation_record.py#L11-L42)
- [backend/app/models/query_task.py:11-39](file://backend/app/models/query_task.py#L11-L39)

**章节来源**
- [backend/app/main.py:13-22](file://backend/app/main.py#L13-L22)
- [backend/app/workers/scheduler.py:25-40](file://backend/app/workers/scheduler.py#L25-L40)
- [frontend/lib/platforms.ts:1-23](file://frontend/lib/platforms.ts#L1-L23)

## 核心组件
- **Kimi适配器（搜索引擎模式）**：基于HTTP客户端的轻量级适配器，通过fetch_search_content获取搜索结果。
- **搜索引擎模块**：提供DuckDuckGo HTML搜索和Wikipedia API回退机制，支持内容提取和清理。
- **引用检测引擎**：编排多平台查询，执行品牌匹配与竞争品牌检测，并持久化结果。
- **定时调度器**：周期性扫描到期查询，触发执行并更新任务状态。
- **数据模型**：查询、引用记录、查询任务三者构成完整的查询生命周期与结果存储。
- **API与服务**：提供REST接口与业务服务，支撑前端展示与手动触发"立即执行"。

**章节来源**
- [backend/app/workers/platforms/kimi.py:10-37](file://backend/app/workers/platforms/kimi.py#L10-L37)
- [backend/app/workers/platforms/search_engine.py:163-174](file://backend/app/workers/platforms/search_engine.py#L163-L174)
- [backend/app/workers/citation_engine.py:161-173](file://backend/app/workers/citation_engine.py#L161-L173)
- [backend/app/workers/scheduler.py:25-40](file://backend/app/workers/scheduler.py#L25-L40)
- [backend/app/models/query.py:11-55](file://backend/app/models/query.py#L11-L55)
- [backend/app/models/citation_record.py:11-42](file://backend/app/models/citation_record.py#L11-L42)
- [backend/app/models/query_task.py:11-39](file://backend/app/models/query_task.py#L11-L39)

## 架构总览
下图展示了从用户发起查询到结果入库的关键路径，以及Kimi适配器在其中的角色。现在Kimi适配器通过搜索引擎获取内容，而非直接操作浏览器。

```mermaid
sequenceDiagram
participant FE as "前端"
participant API as "后端API"
participant SVC as "服务层"
participant SCH as "调度器"
participant ENG as "引用检测引擎"
participant ADP as "Kimi适配器"
participant SE as "搜索引擎模块"
FE->>API : "POST /api/v1/queries 或 /api/v1/citations/run-now"
API->>SVC : "创建查询/立即执行"
SVC->>SCH : "登记查询任务"
SCH->>ENG : "周期性执行查询"
ENG->>ADP : "query(keyword)"
ADP->>SE : "fetch_search_content(platform_name, keyword)"
SE->>SE : "search_duckduckgo 或 Wikipedia"
SE-->>ADP : "返回搜索结果文本"
ADP-->>ENG : "返回原始响应文本"
ENG-->>SVC : "品牌匹配/竞争品牌检测"
SVC-->>API : "写入引用记录"
API-->>FE : "返回查询结果/统计"
```

**图表来源**
- [backend/app/api/queries.py:26-39](file://backend/app/api/queries.py#L26-L39)
- [backend/app/api/citations.py:59-77](file://backend/app/api/citations.py#L59-L77)
- [backend/app/services/query.py:45-81](file://backend/app/services/query.py#L45-L81)
- [backend/app/services/citation.py:204-234](file://backend/app/services/citation.py#L204-L234)
- [backend/app/workers/scheduler.py:51-84](file://backend/app/workers/scheduler.py#L51-L84)
- [backend/app/workers/citation_engine.py:256-287](file://backend/app/workers/citation_engine.py#L256-L287)
- [backend/app/workers/platforms/kimi.py:16-33](file://backend/app/workers/platforms/kimi.py#L16-L33)
- [backend/app/workers/platforms/search_engine.py:163-174](file://backend/app/workers/platforms/search_engine.py#L163-L174)

## 详细组件分析

### Kimi适配器（搜索引擎模式）
**更新** Kimi适配器已完全简化，移除了Playwright浏览器自动化实现，现在是一个轻量级的HTTP客户端适配器。

- **适配器实现**
  - 继承自BasePlatformAdapter基类，实现query和_close方法
  - platform_name设置为"kimi"，platform_url设置为"https://kimi.moonshot.cn"
  - query方法包含最多3次重试（共3次尝试），使用指数退避策略
- **搜索引擎集成**
  - _do_query方法调用fetch_search_content获取搜索结果
  - 通过search_duckduckgo获取HTML搜索结果，必要时回退到Wikipedia
  - 返回清理后的搜索结果文本
- **资源管理**
  - close方法为空实现，因为无额外资源需要释放
  - 依赖httpx异步客户端自动管理连接池

```mermaid
flowchart TD
Start(["进入 query(keyword)"]) --> RetryLoop{"重试次数 < 3"}
RetryLoop --> |是| DoQuery["_do_query 执行单次查询"]
DoQuery --> Success["返回搜索结果文本"]
DoQuery --> |异常| Delay["指数退避等待 (2^attempt)"]
Delay --> RetryLoop
RetryLoop --> |否| FinalFail["记录最终失败并抛出异常"]
Success --> End(["结束"])
FinalFail --> End
```

**图表来源**
- [backend/app/workers/platforms/kimi.py:16-29](file://backend/app/workers/platforms/kimi.py#L16-L29)
- [backend/app/workers/platforms/kimi.py:31-33](file://backend/app/workers/platforms/kimi.py#L31-L33)

**章节来源**
- [backend/app/workers/platforms/kimi.py:10-37](file://backend/app/workers/platforms/kimi.py#L10-L37)

### 搜索引擎模块（Search Engine Module）
**新增** 新增的通用搜索引擎模块，提供DuckDuckGo和Wikipedia的搜索功能。

- **DuckDuckGo搜索**
  - 使用HTML版本搜索，无需API密钥
  - 支持多种结果块匹配策略（标准result块和备选匹配）
  - 自动清理HTML标签和实体，提取可读文本
- **Wikipedia回退机制**
  - 当DuckDuckGo受限或失败时自动回退到Wikipedia API
  - 使用Wikipedia API获取词条摘要，避免HTML解析复杂性
  - 支持引用标记清理和文本格式化
- **内容提取与清理**
  - 统一的HTML标签清理函数
  - 文本压缩和空白字符标准化
  - 最大字符长度控制和结果截断

```mermaid
flowchart TD
Start(["fetch_search_content"]) --> DDG["search_duckduckgo"]
DDG --> Valid{"有效结果?"}
Valid --> |是| Clean["清理HTML标签"]
Clean --> Return["返回结果"]
Valid --> |否| Wiki["search_wikipedia 回退"]
Wiki --> WikiValid{"Wikipedia结果?"}
WikiValid --> |是| WikiClean["清理Wikipedia内容"]
WikiClean --> Return
WikiValid --> |否| Error["抛出所有搜索源失败"]
```

**图表来源**
- [backend/app/workers/platforms/search_engine.py:163-174](file://backend/app/workers/platforms/search_engine.py#L163-L174)
- [backend/app/workers/platforms/search_engine.py:79-144](file://backend/app/workers/platforms/search_engine.py#L79-L144)
- [backend/app/workers/platforms/search_engine.py:16-76](file://backend/app/workers/platforms/search_engine.py#L16-L76)

**章节来源**
- [backend/app/workers/platforms/search_engine.py:163-174](file://backend/app/workers/platforms/search_engine.py#L163-L174)
- [backend/app/workers/platforms/search_engine.py:79-144](file://backend/app/workers/platforms/search_engine.py#L79-L144)
- [backend/app/workers/platforms/search_engine.py:16-76](file://backend/app/workers/platforms/search_engine.py#L16-L76)

### 引用检测引擎（Citation Engine）
- **平台编排**
  - 维护平台适配器映射，支持Kimi、文心、通义等平台
  - 针对每个查询遍历指定平台，执行查询与检测
  - **更新** 所有平台现在共享相同的搜索引擎基础设施
- **品牌匹配**
  - 支持精确匹配、别名匹配、模糊匹配三种策略，输出置信度与首次出现位置
- **竞争品牌检测**
  - 基于预设行业品牌库，识别文本中除目标品牌外的其他品牌
- **任务状态管理**
  - 为每次平台查询创建或获取对应任务记录，维护状态与错误信息
- **结果持久化**
  - 将引用检测结果写入引用记录表，包含是否引用、位置、文本片段、竞争品牌及原始响应

```mermaid
classDiagram
class CitationEngine {
+execute_query(query, db) list
+execute_single_platform(keyword, platform, target_brand, aliases) dict
+close() void
}
class KimiAdapter {
+query(keyword) str
+_do_query(keyword) str
+close() void
}
class SearchEngineModule {
+fetch_search_content(platform_name, keyword) str
+search_duckduckgo(query) str
+search_wikipedia(keyword) str
}
class BrandMatcher {
+match(text) dict
}
class CompetitorDetector {
+detect(text, target_brand) list
}
CitationEngine --> KimiAdapter : "依赖"
CitationEngine --> BrandMatcher : "使用"
CitationEngine --> CompetitorDetector : "使用"
KimiAdapter --> SearchEngineModule : "使用"
```

**图表来源**
- [backend/app/workers/citation_engine.py:161-173](file://backend/app/workers/citation_engine.py#L161-L173)
- [backend/app/workers/platforms/kimi.py:10-37](file://backend/app/workers/platforms/kimi.py#L10-L37)
- [backend/app/workers/platforms/search_engine.py:163-174](file://backend/app/workers/platforms/search_engine.py#L163-L174)

**章节来源**
- [backend/app/workers/citation_engine.py:161-173](file://backend/app/workers/citation_engine.py#L161-L173)
- [backend/app/workers/citation_engine.py:256-287](file://backend/app/workers/citation_engine.py#L256-L287)

### 定时调度器（Scheduler）
- **触发机制**
  - 使用APScheduler的AsyncIOScheduler，每小时检查一次到期查询
  - 条件：查询状态为激活且next_query_at小于等于当前时间
- **执行流程**
  - 逐条执行查询，调用引用检测引擎，更新任务状态与查询时间字段
- **关闭流程**
  - 应用关闭时停止调度器并关闭各平台适配器

```mermaid
sequenceDiagram
participant S as "调度器"
participant DB as "数据库"
participant ENG as "引用检测引擎"
participant ADP as "Kimi适配器"
S->>DB : "查询到期的查询记录"
loop 遍历查询
S->>ENG : "execute_query(query, db)"
ENG->>ADP : "query(keyword)"
ADP->>ADP : "fetch_search_content"
ADP-->>ENG : "返回搜索结果"
ENG-->>S : "写入引用记录并更新时间"
end
S->>S : "关闭时调用 engine.close()"
```

**图表来源**
- [backend/app/workers/scheduler.py:51-84](file://backend/app/workers/scheduler.py#L51-L84)
- [backend/app/workers/citation_engine.py:323-330](file://backend/app/workers/citation_engine.py#L323-L330)

**章节来源**
- [backend/app/workers/scheduler.py:25-40](file://backend/app/workers/scheduler.py#L25-L40)
- [backend/app/workers/scheduler.py:51-84](file://backend/app/workers/scheduler.py#L51-L84)
- [backend/app/workers/scheduler.py:86-90](file://backend/app/workers/scheduler.py#L86-L90)

### 数据模型（Models）
- **查询（Query）**
  - 关键字段：关键词、目标品牌、品牌别名、平台列表、频率、状态、下次查询时间等
- **引用记录（CitationRecord）**
  - 关键字段：是否引用、引用位置、引用文本、竞争品牌列表、原始响应、查询时间等
- **查询任务（QueryTask）**
  - 关键字段：状态、错误信息、计划/开始/完成时间等

```mermaid
erDiagram
QUERY {
uuid id PK
uuid user_id FK
string keyword
string target_brand
jsonb brand_aliases
jsonb platforms
string frequency
string status
timestamp last_queried_at
timestamp next_query_at
timestamp created_at
timestamp updated_at
}
CITATION_RECORD {
uuid id PK
uuid query_id FK
string platform
boolean cited
int citation_position
text citation_text
jsonb competitor_brands
text raw_response
timestamp queried_at
}
QUERY_TASK {
uuid id PK
uuid query_id FK
string platform
string status
text error_message
timestamp scheduled_at
timestamp started_at
timestamp completed_at
}
QUERY ||--o{ CITATION_RECORD : "拥有"
QUERY ||--o{ QUERY_TASK : "拥有"
```

**图表来源**
- [backend/app/models/query.py:11-55](file://backend/app/models/query.py#L11-L55)
- [backend/app/models/citation_record.py:11-42](file://backend/app/models/citation_record.py#L11-L42)
- [backend/app/models/query_task.py:11-39](file://backend/app/models/query_task.py#L11-L39)

**章节来源**
- [backend/app/models/query.py:11-55](file://backend/app/models/query.py#L11-L55)
- [backend/app/models/citation_record.py:11-42](file://backend/app/models/citation_record.py#L11-L42)
- [backend/app/models/query_task.py:11-39](file://backend/app/models/query_task.py#L11-L39)

### API与服务（API & Services）
- **查询API**
  - 支持分页列出、创建、查询详情、更新、删除查询
- **引用API**
  - 支持分页列出引用、统计查询、立即执行查询（触发任务）
- **服务层**
  - 查询服务：校验用户配额、计算下次查询时间、增删改查
  - 引用服务：权限校验、统计聚合、CSV导出、立即执行任务登记

**章节来源**
- [backend/app/api/queries.py:15-86](file://backend/app/api/queries.py#L15-L86)
- [backend/app/api/citations.py:25-77](file://backend/app/api/citations.py#L25-L77)
- [backend/app/services/query.py:12-130](file://backend/app/services/query.py#L12-L130)
- [backend/app/services/citation.py:24-269](file://backend/app/services/citation.py#L24-L269)

## 依赖关系分析
- **组件耦合**
  - 引用检测引擎对Kimi适配器存在直接依赖；对品牌匹配器与竞争品牌检测器为组合关系
  - **更新** Kimi适配器现在依赖通用搜索引擎模块，而非Playwright
  - 定时调度器仅通过引擎接口触发执行，解耦具体平台实现
- **外部依赖**
  - httpx异步HTTP客户端（用于搜索引擎请求）
  - DuckDuckGo和Wikipedia API（无需认证）
  - PostgreSQL数据库（SQLAlchemy ORM）
  - APScheduler（异步调度）
- **循环依赖**
  - 未发现循环导入或调用链路

```mermaid
graph LR
SCH["调度器"] --> ENG["引用检测引擎"]
ENG --> ADP["Kimi适配器"]
ENG --> BM["品牌匹配器"]
ENG --> CD["竞争品牌检测器"]
ADP --> SE["搜索引擎模块"]
SE --> HTTPX["httpx客户端"]
APIQ["查询API"] --> SVQ["查询服务"]
APIC["引用API"] --> SVC["引用服务"]
SVQ --> DB["数据库"]
SVC --> DB
```

**图表来源**
- [backend/app/workers/scheduler.py:25-40](file://backend/app/workers/scheduler.py#L25-L40)
- [backend/app/workers/citation_engine.py:161-173](file://backend/app/workers/citation_engine.py#L161-L173)
- [backend/app/workers/platforms/kimi.py:10-37](file://backend/app/workers/platforms/kimi.py#L10-L37)
- [backend/app/workers/platforms/search_engine.py:163-174](file://backend/app/workers/platforms/search_engine.py#L163-L174)
- [backend/app/api/queries.py:15-86](file://backend/app/api/queries.py#L15-L86)
- [backend/app/api/citations.py:25-77](file://backend/app/api/citations.py#L25-L77)

**章节来源**
- [backend/app/workers/scheduler.py:25-40](file://backend/app/workers/scheduler.py#L25-L40)
- [backend/app/workers/citation_engine.py:161-173](file://backend/app/workers/citation_engine.py#L161-L173)
- [backend/app/api/queries.py:15-86](file://backend/app/api/queries.py#L15-L86)
- [backend/app/api/citations.py:25-77](file://backend/app/api/citations.py#L25-L77)

## 性能考虑
- **HTTP客户端优化**
  - 使用httpx异步客户端，支持连接池复用和超时控制
  - DuckDuckGo搜索超时设置为30秒，避免长时间阻塞
- **搜索策略优化**
  - 首选DuckDuckGo HTML搜索，自动回退到Wikipedia API
  - 支持最多5个结果的提取，平衡准确性和性能
- **重试与退避**
  - 已采用指数退避（2^attempt），建议结合平台可用性监控动态调整重试上限
- **数据库索引**
  - 查询模型的索引设计有助于高频筛选
  - 建议在引用记录表上针对查询时间、平台、是否引用等字段建立复合索引以提升统计与导出性能
- **前端平台映射**
  - 前端平台键值映射清晰，便于UI展示与用户选择

**章节来源**
- [backend/app/workers/platforms/kimi.py:16-29](file://backend/app/workers/platforms/kimi.py#L16-L29)
- [backend/app/workers/platforms/search_engine.py:94-96](file://backend/app/workers/platforms/search_engine.py#L94-L96)
- [backend/app/models/query.py:50-54](file://backend/app/models/query.py#L50-L54)
- [backend/app/models/citation_record.py:37-41](file://backend/app/models/citation_record.py#L37-L41)
- [frontend/lib/platforms.ts:1-23](file://frontend/lib/platforms.ts#L1-L23)

## 故障排查指南
- **DuckDuckGo限制**
  - 现象：DuckDuckGo返回非结果页面或解析失败
  - 处理：自动回退到Wikipedia API；检查网络连接和代理设置
- **Wikipedia API失败**
  - 现象：Wikipedia API调用失败或无结果
  - 处理：检查Wikipedia API可用性；确认关键词有效性
- **搜索结果为空**
  - 现象：两个搜索源均无结果
  - 处理：尝试更具体的关键词；检查网络连接
- **重试仍失败**
  - 现象：多次重试后仍失败
  - 处理：查看日志中的最后一次错误；检查平台访问限制与验证码弹窗
- **API调用失败**
  - 现象：接口返回4xx/5xx错误
  - 处理：核对鉴权头与请求体；检查用户权限与查询状态；查看服务端日志定位异常

**章节来源**
- [backend/app/workers/platforms/search_engine.py:139-144](file://backend/app/workers/platforms/search_engine.py#L139-L144)
- [backend/app/workers/platforms/search_engine.py:140-144](file://backend/app/workers/platforms/search_engine.py#L140-L144)
- [backend/app/workers/platforms/kimi.py:24-29](file://backend/app/workers/platforms/kimi.py#L24-L29)
- [backend/app/api/queries.py:34-38](file://backend/app/api/queries.py#L34-L38)
- [backend/app/api/citations.py:67-71](file://backend/app/api/citations.py#L67-L71)

## 结论
Kimi适配器已成功简化为搜索引擎模式，移除了复杂的Playwright浏览器自动化实现，转而采用轻量级的HTTP客户端方案。通过DuckDuckGo和Wikipedia的组合搜索策略，实现了稳定的搜索结果获取。新的架构更加简洁、高效，降低了维护成本，同时保持了与原有系统的兼容性。建议在生产环境中关注HTTP客户端性能、搜索策略优化和数据库索引设计。

## 附录

### API调用示例（路径参考）
- **创建查询**
  - POST /api/v1/queries
  - 请求体字段：keyword、target_brand、brand_aliases、platforms、frequency
  - 成功响应：QueryResponse
- **立即执行查询**
  - POST /api/v1/queries/{query_id}/run-now
  - 成功响应：RunNowResponse（包含任务ID与状态）
- **查询引用列表**
  - GET /api/v1/citations/?query_id={query_id}&platform={platform}
  - 成功响应：CitationListResponse
- **引用统计**
  - GET /api/v1/citations/stats?query_id={query_id}
  - 成功响应：CitationStatsResponse

**章节来源**
- [backend/app/api/queries.py:26-39](file://backend/app/api/queries.py#L26-L39)
- [backend/app/api/citations.py:59-77](file://backend/app/api/citations.py#L59-L77)
- [backend/app/api/citations.py:25-56](file://backend/app/api/citations.py#L25-L56)