Ollama + AnythingLLM + Qwen 2.5 → PanAI 3.5 集成架构

整体定位:双轨 LLM 路由

用户查询


PanAI 3.5 Query Router (FastAPI)
├── 本地轨道: Ollama / Qwen 2.5 ← 私密文献、离线、中文粗检索
└── 云端轨道: Claude API ← 深度神学推理、英文精析、Golden Path

两者不是替代关系,而是分工路由:Qwen 2.5 处理高频、私密、低成本任务;Claude 处理需要神学推理深度的任务。

层次一:Ollama 作为 PanAI 的备用 LLM Provider

Ollama 提供 OpenAI 兼容接口,可直接替换 Claude API 调用:

# FastAPI: llm_provider.py
import httpx
from enum import Enum

class LLMProvider(Enum):
CLAUDE = “claude”
QWEN_LOCAL = “qwen_local”

async def call_llm(
prompt: str,
provider: LLMProvider = LLMProvider.CLAUDE,
model: str = None
) -> str:

if provider == LLMProvider.CLAUDE:
# 现有 Claude API 调用
return await call_claude(prompt)

elif provider == LLMProvider.QWEN_LOCAL:
# Ollama 兼容 OpenAI 格式
response = await httpx.AsyncClient().post(
http://localhost:11434/v1/chat/completions“,
json={
“model”: model or “qwen2.5:14b”,
“messages”: [{“role”: “user”, “content”: prompt}],
“temperature”: 0.3,
“stream”: False
}
)
return response.json()[“choices”][0][“message”][“content”]

路由策略(在 FastAPI 的 /query 端点中):

def select_provider(query: QueryRequest) -> LLMProvider:
# 规则1: 包含个人/内部文档 → 本地
if query.filter.get(“collection”) == “internal”:
return LLMProvider.QWEN_LOCAL

# 规则2: 简单事实检索(含”是什么”、”哪一卷”) → 本地
if is_factual_lookup(query.text):
return LLMProvider.QWEN_LOCAL

# 规则3: 神学推理、Golden Path 分析 → Claude
if query.mode in [“golden_path”, “scripture_alignment”, “deep_analysis”]:
return LLMProvider.CLAUDE

# 默认 → Claude
return LLMProvider.CLAUDE

层次二:AnythingLLM 作为文档工作区前端

AnythingLLM 自带 REST API(默认端口 3001),可桥接到 PanAI 的文档库:

2a. 配置 AnythingLLM 使用 Ollama + Qwen 2.5

在 AnythingLLM 设置中:

LLM Provider → Ollama
Ollama URL → http://localhost:11434
Model → qwen2.5:14b
Embedding → nomic-embed-text (via Ollama)
Vector DB → LanceDB (本地) 或 指向你的 Elasticsearch

2b. PanAI 调用 AnythingLLM API

# 将 AnythingLLM workspace 作为文档问答子系统
async def query_anythingllm(
workspace_slug: str,
question: str
) -> dict:
async with httpx.AsyncClient() as client:
response = await client.post(
f”http://localhost:3001/api/v1/workspace/{workspace_slug}/chat“,
headers={“Authorization”: f”Bearer {ANYTHINGLLM_API_KEY}”},
json={
“message”: question,
“mode”: “chat” # 或 “query”(无历史记忆)
}
)
data = response.json()
return {
“answer”: data[“textResponse”],
“sources”: data.get(“sources”, []),
“provider”: “anythingllm/qwen2.5”
}

2c. 文档同步:PanAI → AnythingLLM

# 将 Elasticsearch 中的文档同步到 AnythingLLM workspace
async def sync_docs_to_anythingllm(es_index: str, workspace: str):
# 1. 从 ES 拉取文档
docs = await es.search(index=es_index, body={“query”: {“match_all”: {}}})

# 2. 上传到 AnythingLLM
for hit in docs[“hits”][“hits”]:
await httpx.post(
http://localhost:3001/api/v1/document/raw-text“,
headers={“Authorization”: f”Bearer {ANYTHINGLLM_API_KEY}”},
json={
“textContent”: hit[“_source”][“content”],
“metadata”: {
“title”: hit[“_source”][“title”],
“author”: hit[“_source”][“author”],
“tradition”: “nee-lee”
}
}
)

层次三:完整集成架构图

┌─────────────────────────────────────────────────────┐
│ PanAI 3.5 (FastAPI) │
│ │
│ /query ──► QueryRouter │
│ ├── [深度分析] ──► Claude API │
│ ├── [事实检索] ──► Ollama/Qwen2.5 │
│ └── [文档问答] ──► AnythingLLM API │
│ │
│ /ingest ──► DocumentPipeline │
│ ├── Elasticsearch (全文索引) │
│ ├── Neo4j (知识图谱) │
│ └── AnythingLLM (本地向量库) ◄── Sync │
└─────────────────────────────────────────────────────┘
│ │
▼ ▼
Claude API Ollama (localhost:11434)
(claude-sonnet) └── Qwen2.5:14b

AnythingLLM (localhost:3001)
└── 向量检索 + workspace管理

Qwen 2.5 的特定适用场景(对 PanAI 3.5)

|任务类型 |Qwen 2.5 |Claude|
|————————-|————|——|
|中文文献段落检索 |✅ 优先 |备用 |
|李常受信息摘要 |✅ 可用 |精析时用 |
|Golden Path 五步提取 |⚠️ 需微调 prompt|✅ 优先 |
|Scripture Alignment L1-L5|❌ |✅ 必须 |
|四维有机框架评分 |❌ |✅ 必须 |
|简单事实问答 |✅ 低成本 |浪费 |

快速启动清单

# 1. 安装 Qwen 2.5
ollama pull qwen2.5:14b
ollama pull nomic-embed-text # 嵌入模型

# 2. 验证 Ollama API
curl http://localhost:11434/v1/models

# 3. AnythingLLM 配置后获取 API Key
# 设置 → 工具 → API → 生成 Key

# 4. 在 PanAI .env 中添加
OLLAMA_BASE_URL=http://localhost:11434
ANYTHINGLLM_URL=http://localhost:3001
ANYTHINGLLM_API_KEY=your_key
ANYTHINGLLM_WORKSPACE=nee-lee-ministry

下一步建议:先完成层次一(LLM 路由抽象),这是成本最低、收益最快的集成点。你现在 PanAI 3.5 的 LLM 调用是否已经抽象为统一接口,还是直接硬编码 Claude?