Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • 搜索未来:SEO与GEO双引擎实战手册
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • Rust 开发入门
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • 搜索未来:SEO与GEO双引擎实战手册
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • Rust 开发入门
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 附录F:本地模拟生成式引擎环境(Docker Compose + Ollama)

附录F:本地模拟生成式引擎环境(Docker Compose + Ollama)

F.1 概述与目的

在生成式引擎优化(GEO)的实战中,能够在本地模拟目标生成式搜索引擎(如 Perplexity、Bing Chat、豆包、DeepSeek)的引用与回答逻辑,是工程师进行策略验证、内容调试和效果预判的核心能力。

本附录提供一套基于 Docker Compose 和 Ollama 的本地模拟环境搭建方案。该环境可以:

  • 在完全离线或内网环境中运行。
  • 加载主流开源大模型(如 LLaMA、Mistral、Qwen)。
  • 通过 RAG(检索增强生成)模拟生成式引擎的“检索+回答”流程。
  • 用于测试你的内容被模型引用和生成答案的可能性。

F.2 环境架构

+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Docker Compose  |       |   Ollama Server   |       |   RAG 应用层      |
|   (编排层)        | ----> |   (模型推理层)     | ----> |   (模拟引擎)      |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+
        |                           |                           |
        | 定义服务、网络、卷         | 管理模型下载与推理         | 实现检索、Prompt、输出
        |                           |                           |
        v                           v                           v
+-------------------+       +-------------------+       +-------------------+
|   ChromaDB        |       |   Nginx 反向代理   |       |   Web UI (可选)   |
|   (向量数据库)    |       |   (路由与日志)     |       |   (Open WebUI)    |
+-------------------+       +-------------------+       +-------------------+

F.3 环境准备

3.1 系统要求

  • 操作系统:Linux / macOS / Windows(WSL2)
  • Docker:20.10+
  • Docker Compose:v2.0+
  • 内存:建议 ≥ 16GB(运行 7B 模型至少需要 8GB)
  • 磁盘:模型文件约 4-8GB(7B 模型)

3.2 安装 Docker 与 Docker Compose

# Ubuntu/Debian
sudo apt update
sudo apt install docker.io docker-compose-v2

# macOS (使用 Homebrew)
brew install docker docker-compose

# Windows (使用 WSL2 + Docker Desktop)
# 参考官方文档:https://docs.docker.com/desktop/wsl/

3.3 验证安装

docker --version
docker compose version

F.4 快速部署方案

4.1 项目目录结构

geo-local-engine/
├── docker-compose.yml
├── .env
├── ollama/
│   └── Dockerfile (可选,用于自定义)
├── chromadb/
│   └── data/          # 向量数据库持久化
├── rag-app/
│   ├── Dockerfile
│   ├── app.py         # RAG 核心逻辑
│   └── requirements.txt
├── nginx/
│   ├── nginx.conf
│   └── logs/
└── data/
    └── your_content/  # 放入你的待测试内容

4.2 docker-compose.yml 文件

version: '3.8'

services:
  # Ollama 服务 - 模型推理引擎
  ollama:
    image: ollama/ollama:latest
    container_name: geo-ollama
    ports:
      - "11434:11434"
    volumes:
      - ./ollama/data:/root/.ollama
    environment:
      - OLLAMA_KEEP_ALIVE=24h
      - OLLAMA_NUM_PARALLEL=4
      - OLLAMA_MAX_LOADED_MODELS=2
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]  # 如有GPU,取消注释
    restart: unless-stopped
    networks:
      - geo-net

  # ChromaDB - 向量数据库(存储内容嵌入)
  chromadb:
    image: chromadb/chroma:latest
    container_name: geo-chromadb
    ports:
      - "8000:8000"
    volumes:
      - ./chromadb/data:/chroma/chroma
    environment:
      - IS_PERSISTENT=TRUE
      - PERSIST_DIRECTORY=/chroma/chroma
      - ANONYMIZED_TELEMETRY=FALSE
    restart: unless-stopped
    networks:
      - geo-net

  # RAG 应用 - 模拟生成引擎
  rag-app:
    build: ./rag-app
    container_name: geo-rag-app
    ports:
      - "8080:8080"
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
      - CHROMA_HOST=chromadb
      - CHROMA_PORT=8000
      - MODEL_NAME=qwen2.5:7b  # 可改为其他模型
      - EMBEDDING_MODEL=bge-m3:latest
      - COLLECTION_NAME=geo_test
    volumes:
      - ./data:/app/data
    depends_on:
      - ollama
      - chromadb
    restart: unless-stopped
    networks:
      - geo-net

  # Nginx 反向代理(可选,用于日志和路由)
  nginx:
    image: nginx:alpine
    container_name: geo-nginx
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/logs:/var/log/nginx
    depends_on:
      - rag-app
    restart: unless-stopped
    networks:
      - geo-net

  # Open WebUI(可选,提供图形界面)
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: geo-webui
    ports:
      - "3000:8080"
    volumes:
      - ./open-webui/data:/app/backend/data
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
    depends_on:
      - ollama
    restart: unless-stopped
    networks:
      - geo-net

networks:
  geo-net:
    driver: bridge

4.3 .env 配置文件

# 模型选择
# 中文推荐:qwen2.5:7b, qwen2.5:14b, yi:34b
# 英文推荐:llama3.1:8b, mistral:7b, mixtral:8x7b
MODEL_NAME=qwen2.5:7b
EMBEDDING_MODEL=bge-m3:latest

# Ollama 配置
OLLAMA_KEEP_ALIVE=24h
OLLAMA_NUM_PARALLEL=4

# ChromaDB 配置
CHROMA_PERSIST_DIR=/chroma/chroma

F.5 RAG 应用核心代码

5.1 app.py(简化版)

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import ollama
import chromadb
from chromadb.utils import embedding_functions
import os
import logging

app = FastAPI(title="本地生成引擎模拟器")

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 环境变量
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://ollama:11434")
CHROMA_HOST = os.getenv("CHROMA_HOST", "chromadb")
CHROMA_PORT = int(os.getenv("CHROMA_PORT", "8000"))
MODEL_NAME = os.getenv("MODEL_NAME", "qwen2.5:7b")
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "bge-m3:latest")
COLLECTION_NAME = os.getenv("COLLECTION_NAME", "geo_test")

# 初始化 Ollama 客户端
ollama_client = ollama.Client(host=OLLAMA_BASE_URL)

# 初始化 ChromaDB 客户端
chroma_client = chromadb.HttpClient(host=CHROMA_HOST, port=CHROMA_PORT)
embedding_func = embedding_functions.OllamaEmbeddingFunction(
    model_name=EMBEDDING_MODEL,
    url=OLLAMA_BASE_URL
)

# 获取或创建集合
try:
    collection = chroma_client.get_collection(
        name=COLLECTION_NAME,
        embedding_function=embedding_func
    )
    logger.info(f"已加载现有集合: {COLLECTION_NAME}")
except:
    collection = chroma_client.create_collection(
        name=COLLECTION_NAME,
        embedding_function=embedding_func
    )
    logger.info(f"已创建新集合: {COLLECTION_NAME}")

class QueryRequest(BaseModel):
    query: str
    top_k: int = 5
    use_rag: bool = True
    system_prompt: str = "你是一个专业的搜索助手。请基于提供的上下文信息,用中文回答用户问题。如果信息不足,请明确说明。"

class IngestRequest(BaseModel):
    file_path: str = "/app/data"
    chunk_size: int = 500
    chunk_overlap: int = 50

@app.get("/health")
async def health():
    """健康检查"""
    return {"status": "ok", "model": MODEL_NAME, "collection": COLLECTION_NAME}

@app.post("/ingest")
async def ingest_content(request: IngestRequest):
    """导入内容到向量数据库"""
    import glob
    from langchain.text_splitter import RecursiveCharacterTextSplitter
    
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=request.chunk_size,
        chunk_overlap=request.chunk_overlap
    )
    
    files = glob.glob(f"{request.file_path}/**/*.md", recursive=True) + \
            glob.glob(f"{request.file_path}/**/*.txt", recursive=True) + \
            glob.glob(f"{request.file_path}/**/*.html", recursive=True)
    
    if not files:
        raise HTTPException(status_code=404, detail="未找到可导入的文件")
    
    imported_count = 0
    for file_path in files:
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
            
            chunks = text_splitter.split_text(content)
            
            for i, chunk in enumerate(chunks):
                collection.add(
                    documents=[chunk],
                    metadatas=[{
                        "source": file_path,
                        "chunk_index": i,
                        "total_chunks": len(chunks)
                    }],
                    ids=[f"{file_path}_{i}"]
                )
                imported_count += 1
                
            logger.info(f"已导入: {file_path} ({len(chunks)} 块)")
        except Exception as e:
            logger.error(f"导入失败: {file_path} - {str(e)}")
    
    return {"imported_chunks": imported_count, "files_processed": len(files)}

@app.post("/query")
async def query(request: QueryRequest):
    """模拟生成引擎的问答流程"""
    # 步骤1: 检索相关上下文
    if request.use_rag:
        results = collection.query(
            query_texts=[request.query],
            n_results=request.top_k
        )
        
        if results['documents'] and results['documents'][0]:
            context = "\n\n".join([
                f"[来源: {meta['source']}]\n{doc}"
                for doc, meta in zip(results['documents'][0], results['metadatas'][0])
            ])
            
            sources = [
                {
                    "content": doc[:200] + "...",
                    "source": meta['source'],
                    "relevance": score
                }
                for doc, meta, score in zip(
                    results['documents'][0],
                    results['metadatas'][0],
                    results['distances'][0]
                )
            ]
        else:
            context = "未找到相关上下文。"
            sources = []
    else:
        context = ""
        sources = []
    
    # 步骤2: 构建 Prompt
    messages = [
        {"role": "system", "content": request.system_prompt},
        {"role": "user", "content": f"上下文信息:\n{context}\n\n用户问题:{request.query}"}
    ]
    
    # 步骤3: 调用 Ollama 生成回答
    try:
        response = ollama_client.chat(
            model=MODEL_NAME,
            messages=messages,
            options={
                "temperature": 0.3,
                "top_p": 0.9,
                "max_tokens": 2048
            }
        )
        
        return {
            "query": request.query,
            "answer": response['message']['content'],
            "sources": sources,
            "model": MODEL_NAME,
            "rag_enabled": request.use_rag
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"模型推理失败: {str(e)}")

@app.post("/compare")
async def compare_answers(request: QueryRequest):
    """对比有无 RAG 的回答差异"""
    # 有 RAG
    rag_response = await query(QueryRequest(
        query=request.query,
        use_rag=True,
        system_prompt=request.system_prompt
    ))
    
    # 无 RAG(纯模型知识)
    no_rag_response = await query(QueryRequest(
        query=request.query,
        use_rag=False,
        system_prompt=request.system_prompt
    ))
    
    return {
        "query": request.query,
        "with_rag": rag_response,
        "without_rag": no_rag_response
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8080)

5.2 requirements.txt

fastapi==0.104.1
uvicorn[standard]==0.24.0
ollama==0.1.9
chromadb==0.4.22
langchain==0.1.0
pydantic==2.5.2
python-multipart==0.0.6

5.3 Dockerfile(rag-app)

FROM python:3.11-slim

WORKDIR /app

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖文件
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY app.py .

# 创建数据目录
RUN mkdir -p /app/data

# 暴露端口
EXPOSE 8080

# 启动命令
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]

5.4 nginx.conf(可选)

events {
    worker_connections 1024;
}

http {
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log;

    upstream rag_app {
        server rag-app:8080;
    }

    server {
        listen 80;
        server_name localhost;

        location / {
            proxy_pass http://rag_app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /api/ {
            proxy_pass http://rag_app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

F.6 启动与使用

6.1 启动环境

# 进入项目目录
cd geo-local-engine

# 首次启动(会自动拉取镜像)
docker compose up -d

# 查看日志
docker compose logs -f

# 检查服务状态
docker compose ps

6.2 下载模型

# 进入 Ollama 容器
docker exec -it geo-ollama /bin/bash

# 下载中文模型(推荐)
ollama pull qwen2.5:7b
ollama pull bge-m3:latest

# 下载英文模型(可选)
ollama pull llama3.1:8b
ollama pull nomic-embed-text:latest

# 退出容器
exit

6.3 导入待测试内容

# 将你的内容放入 data/ 目录
# 支持格式:.md, .txt, .html

# 调用导入 API
curl -X POST http://localhost:8080/ingest \
  -H "Content-Type: application/json" \
  -d '{
    "file_path": "/app/data",
    "chunk_size": 500,
    "chunk_overlap": 50
  }'

6.4 模拟查询

# 基础查询(带 RAG)
curl -X POST http://localhost:8080/query \
  -H "Content-Type: application/json" \
  -d '{
    "query": "你的产品的主要功能是什么?",
    "top_k": 5,
    "use_rag": true
  }'

# 对比查询(有/无 RAG)
curl -X POST http://localhost:8080/compare \
  -H "Content-Type: application/json" \
  -d '{
    "query": "你的产品的主要功能是什么?"
  }'

6.5 使用 Open WebUI(图形界面)

访问 http://localhost:3000,首次使用需注册账号。 在设置中选择模型为 qwen2.5:7b。

F.7 高级配置与优化

7.1 使用 GPU 加速

在 docker-compose.yml 中取消 GPU 相关注释:

services:
  ollama:
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

并确保已安装 NVIDIA Container Toolkit:

# Ubuntu
sudo apt install nvidia-container-toolkit
sudo systemctl restart docker

7.2 多模型配置

修改 .env 文件中的 MODEL_NAME:

# 使用不同模型
MODEL_NAME=yi:34b          # 中文大模型(34B)
MODEL_NAME=mixtral:8x7b    # 混合专家模型
MODEL_NAME=deepseek-coder:6.7b  # 代码专用

7.3 自定义系统提示词

通过 API 参数自定义生成风格:

curl -X POST http://localhost:8080/query \
  -H "Content-Type: application/json" \
  -d '{
    "query": "你的产品的主要功能是什么?",
    "system_prompt": "你是一个严谨的技术文档助手。请用结构化方式回答,包含:1)功能概述 2)核心特性 3)使用场景。只基于提供的上下文回答。"
  }'

7.4 批量测试脚本

# batch_test.py
import requests
import json
import time

BASE_URL = "http://localhost:8080"

test_queries = [
    "你的产品的主要功能是什么?",
    "你的产品与竞争对手相比有什么优势?",
    "你的产品如何安装?",
    "你的产品的价格是多少?",
    "你的产品支持哪些平台?"
]

results = []
for query in test_queries:
    response = requests.post(
        f"{BASE_URL}/query",
        json={"query": query, "use_rag": True}
    )
    result = response.json()
    results.append({
        "query": query,
        "answer": result["answer"][:200],
        "sources_count": len(result["sources"])
    })
    print(f"Query: {query}")
    print(f"Answer: {result['answer'][:100]}...")
    print(f"Sources: {len(result['sources'])}")
    print("-" * 50)
    time.sleep(1)

# 保存结果
with open("geo_test_results.json", "w", encoding="utf-8") as f:
    json.dump(results, f, ensure_ascii=False, indent=2)

F.8 监控与调试

8.1 检查服务日志

# 查看所有服务日志
docker compose logs -f

# 查看特定服务日志
docker compose logs rag-app -f
docker compose logs ollama -f

8.2 查看向量数据库内容

# 进入 ChromaDB 容器
docker exec -it geo-chromadb /bin/bash

# 使用 Python 检查
python3 -c "
import chromadb
client = chromadb.HttpClient(host='localhost', port=8000)
collection = client.get_collection('geo_test')
print(f'文档数量: {collection.count()}')
results = collection.get()
print(f'前3个文档: {results[\"documents\"][:3]}')
"

8.3 性能监控

# 查看资源使用
docker stats

# 查看 Ollama 模型加载状态
curl http://localhost:11434/api/tags

F.9 常见问题与排错

9.1 模型下载失败

# 检查网络连接
docker exec geo-ollama curl -I https://ollama.ai

# 使用国内镜像(如适用)
ollama pull qwen2.5:7b --insecure

9.2 内存不足

# 使用更小的模型
ollama pull qwen2.5:1.5b  # 1.5B 参数,仅需 2GB 内存

# 限制并发
# 在 .env 中设置
OLLAMA_NUM_PARALLEL=1
OLLAMA_MAX_LOADED_MODELS=1

9.3 向量数据库连接失败

# 检查 ChromaDB 是否启动
docker compose ps chromadb

# 重启服务
docker compose restart chromadb

9.4 内容导入后查询无结果

# 检查内容是否成功导入
curl http://localhost:8080/health

# 检查集合中的文档数量
# 使用 Python 脚本验证

F.10 实际应用场景

10.1 测试内容在生成引擎中的表现

  • 导入你的产品文档、博客文章、FAQ。
  • 模拟用户提问,观察模型是否引用你的内容。
  • 对比有/无 RAG 的回答差异。

10.2 优化结构化数据

  • 测试不同 Schema 标记对模型理解的影响。
  • 验证 JSON-LD 是否被正确解析。

10.3 竞品分析

  • 导入竞争对手的公开内容。
  • 对比你的内容在相同查询下的表现。

10.4 内容策略验证

  • 测试不同写作风格(如问答式、列表式、段落式)的效果。
  • 验证关键词密度和语义相关性的影响。

F.11 与生产环境的差异说明

特性本地模拟环境真实生成引擎
模型规模7B-34B 参数数百亿参数
训练数据仅你导入的内容全网数据
排名算法简单的向量相似度复杂的多因素排序
实时性手动更新自动爬取
个性化无基于用户历史

建议:将本地环境作为快速验证工具,最终优化策略仍需在真实生成引擎中验证。


F.12 一键部署脚本

创建一个 deploy.sh 脚本,简化部署流程:

#!/bin/bash

# 本地生成引擎环境一键部署脚本

echo "=== 开始部署本地生成引擎环境 ==="

# 1. 检查 Docker
if ! command -v docker &> /dev/null; then
    echo "错误: 请先安装 Docker"
    exit 1
fi

# 2. 创建目录结构
mkdir -p {ollama/data,chromadb/data,nginx/logs,data/your_content,rag-app}

# 3. 创建 docker-compose.yml
cat > docker-compose.yml << 'EOF'
# 在此粘贴上面的 docker-compose.yml 内容
EOF

# 4. 创建 .env 文件
cat > .env << 'EOF'
MODEL_NAME=qwen2.5:7b
EMBEDDING_MODEL=bge-m3:latest
OLLAMA_KEEP_ALIVE=24h
OLLAMA_NUM_PARALLEL=4
EOF

# 5. 启动服务
echo "启动 Docker 服务..."
docker compose up -d

# 6. 等待服务就绪
echo "等待服务就绪..."
sleep 10

# 7. 下载模型
echo "下载模型(这可能需要几分钟)..."
docker exec geo-ollama ollama pull qwen2.5:7b
docker exec geo-ollama ollama pull bge-m3:latest

# 8. 验证
echo "验证服务状态..."
docker compose ps

echo "=== 部署完成 ==="
echo "API 地址: http://localhost:8080"
echo "Web UI: http://localhost:3000"
echo "Ollama API: http://localhost:11434"
echo ""
echo "使用说明:"
echo "1. 将你的内容放入 data/your_content/ 目录"
echo "2. 调用导入 API: curl -X POST http://localhost:8080/ingest"
echo "3. 开始测试查询: curl -X POST http://localhost:8080/query"

通过本附录提供的环境,你可以:

  1. 快速验证内容在生成引擎中的表现
  2. 优化内容策略前进行低成本测试
  3. 调试结构化数据和语义标记
  4. 建立持续集成中的 GEO 质检流程

将此环境纳入你的全栈工具链,将显著提升 GEO 优化的效率和准确性。

Last Updated:: 5/9/26, 5:13 PM