🚀 快速安装

复制以下命令并运行,立即安装此 Skill:

npx skills add https://skills.sh/aradotso/trending-skills/mirofish-offline-simulation

💡 提示:需要 Node.js 和 NPM

MiroFish-Offline 技能

技能由 ara.so 提供 — Daily 2026 Skills 系列。

MiroFish-Offline 是一个完全本地的多智能体群智能引擎。输入任何文档(新闻稿、政策草案、财务报告),它就会生成数百个具有独特个性的 AI 智能体,在社交媒体上模拟公众反应——发帖、争论、观点转变——按小时模拟。无需云 API:Neo4j CE 5.15 处理图内存,Ollama 提供 LLM 服务。


架构概述

文档输入
图构建(通过 Ollama LLM 进行命名实体识别 + 关系提取)
Neo4j 知识图谱(实体、关系、通过 nomic-embed-text 生成的嵌入向量)
环境设置(生成数百个具有个性和记忆的智能体角色)
模拟(智能体在模拟平台上发帖、回复、争论、转变观点)
报告(报告智能体采访焦点小组、查询图谱、生成分析)
交互(与任何单个智能体聊天,完整记忆持久化)

后端: Flask + Python 3.11
前端: Vue 3 + Node 18
图数据库: Neo4j CE 5.15(bolt 协议)
LLM: Ollama(OpenAI 兼容的 /v1 端点)
嵌入模型: nomic-embed-text(768 维,通过 Ollama)
搜索: 混合搜索 — 0.7 × 向量相似度 + 0.3 × BM25


安装

选项 A:Docker(推荐)

git clone https://github.com/nikmcfly/MiroFish-Offline.git
cd MiroFish-Offline
cp .env.example .env

# 启动 Neo4j + Ollama + MiroFish 后端 + 前端
docker compose up -d

# 将所需模型拉取到 Ollama 容器中
docker exec mirofish-ollama ollama pull qwen2.5:32b
docker exec mirofish-ollama ollama pull nomic-embed-text

# 检查所有服务是否健康
docker compose ps

打开 http://localhost:3000

选项 B:手动设置

1. Neo4j

docker run -d --name neo4j \
  -p 7474:7474 -p 7687:7687 \
  -e NEO4J_AUTH=neo4j/mirofish \
  neo4j:5.15-community

2. Ollama

ollama serve &
ollama pull qwen2.5:32b       # 主 LLM(约 20GB,需要 24GB 显存)
ollama pull qwen2.5:14b       # 轻量选项(约 10GB 显存)
ollama pull nomic-embed-text  # 嵌入模型(小,快速)

3. 后端

cp .env.example .env
# 编辑 .env(见配置部分)

cd backend
pip install -r requirements.txt
python run.py
# 后端启动于 http://localhost:5000

4. 前端

cd frontend
npm install
npm run dev
# 前端启动于 http://localhost:3000

配置 (.env)

# ── LLM (Ollama OpenAI 兼容端点) ──────────────────────────
LLM_API_KEY=ollama
LLM_BASE_URL=http://localhost:11434/v1
LLM_MODEL_NAME=qwen2.5:32b

# ── Neo4j ─────────────────────────────────────────────────────────────
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=mirofish

# ── 嵌入模型 (Ollama) ───────────────────────────────────────────────
EMBEDDING_MODEL=nomic-embed-text
EMBEDDING_BASE_URL=http://localhost:11434

# ── 可选:将 Ollama 替换为任何 OpenAI 兼容的提供商 ─────────
# LLM_API_KEY=$OPENAI_API_KEY
# LLM_BASE_URL=https://api.openai.com/v1
# LLM_MODEL_NAME=gpt-4o

核心 Python API

GraphStorage 接口

MiroFish 和图数据库之间的抽象层:

from backend.storage.base import GraphStorage
from backend.storage.neo4j_storage import Neo4jStorage

# 初始化存储(通常通过 Flask app.extensions 完成)
storage = Neo4jStorage(
    uri=os.environ["NEO4J_URI"],
    user=os.environ["NEO4J_USER"],
    password=os.environ["NEO4J_PASSWORD"],
    embedding_model=os.environ["EMBEDDING_MODEL"],
    embedding_base_url=os.environ["EMBEDDING_BASE_URL"],
    llm_base_url=os.environ["LLM_BASE_URL"],
    llm_api_key=os.environ["LLM_API_KEY"],
    llm_model=os.environ["LLM_MODEL_NAME"],
)

从文档构建知识图谱

from backend.services.graph_builder import GraphBuilder

builder = GraphBuilder(storage=storage)

# 输入文档字符串
with open("press_release.txt", "r") as f:
    document_text = f.read()

# 提取实体 + 关系,存储到 Neo4j
graph_id = builder.build(
    content=document_text,
    title="Q4 Earnings Report",
    source_type="financial_report",
)

print(f"图构建完成:{graph_id}")
# 返回用于后续模拟运行的 graph_id

创建并运行模拟

from backend.services.simulation import SimulationService

sim = SimulationService(storage=storage)

# 从现有图谱创建模拟环境
sim_id = sim.create_environment(
    graph_id=graph_id,
    agent_count=200,           # 要生成的智能体数量
    simulation_hours=24,       # 模拟时间跨度
    platform="twitter",        # "twitter" | "reddit" | "weibo"
)

# 运行模拟(阻塞式 — 生产环境使用异步包装器)
result = sim.run(sim_id=sim_id)

print(f"模拟完成。生成的帖子数:{result['post_count']}")
print(f"情感轨迹:{result['sentiment_over_time']}")

查询模拟结果

from backend.services.report import ReportAgent

report_agent = ReportAgent(storage=storage)

# 生成结构化分析报告
report = report_agent.generate(
    sim_id=sim_id,
    focus_group_size=10,    # 要采访的智能体数量
    include_graph_search=True,
)

print(report["summary"])
print(report["key_narratives"])
print(report["sentiment_shift"])
print(report["influential_agents"])

与模拟智能体聊天

from backend.services.agent_chat import AgentChatService

chat = AgentChatService(storage=storage)

# 列出已完成模拟中的智能体
agents = chat.list_agents(sim_id=sim_id, limit=10)
agent_id = agents[0]["id"]

print(f"正在与以下智能体聊天:{agents[0]['persona']['name']}")
print(f"个性:{agents[0]['persona']['traits']}")

# 发送消息 — 智能体以角色身份回复,保留完整记忆
response = chat.send(
    agent_id=agent_id,
    message="你为什么发布那条关于财报的批评性内容?",
)

print(response["reply"])
# → 智能体使用其个性、观点偏见和发帖历史进行回复

知识图谱的混合搜索

from backend.services.search import SearchService

search = SearchService(storage=storage)

# 混合搜索:0.7 * 向量相似度 + 0.3 * BM25
results = search.query(
    text="高管薪酬争议",
    graph_id=graph_id,
    top_k=5,
    vector_weight=0.7,
    bm25_weight=0.3,
)

for r in results:
    print(r["entity"], r["relationship"], r["score"])

实现自定义 GraphStorage 后端

from backend.storage.base import GraphStorage
from typing import List, Dict, Any

class MyCustomStorage(GraphStorage):
    """
    通过实现此接口,将 Neo4j 替换为任何图数据库。
    通过 Flask app.extensions['neo4j_storage'] = MyCustomStorage(...) 注册
    """

    def store_entity(self, entity: Dict[str, Any]) -> str:
        # 存储实体,返回 entity_id
        raise NotImplementedError

    def store_relationship(
        self,
        source_id: str,
        target_id: str,
        relation_type: str,
        properties: Dict[str, Any],
    ) -> str:
        raise NotImplementedError

    def vector_search(
        self, embedding: List[float], top_k: int = 5
    ) -> List[Dict[str, Any]]:
        raise NotImplementedError

    def keyword_search(
        self, query: str, top_k: int = 5
    ) -> List[Dict[str, Any]]:
        raise NotImplementedError

    def get_agent_memory(self, agent_id: str) -> Dict[str, Any]:
        raise NotImplementedError

    def update_agent_memory(
        self, agent_id: str, memory_update: Dict[str, Any]
    ) -> None:
        raise NotImplementedError

Flask 应用集成模式

# backend/app.py — 如何通过依赖注入连接存储
from flask import Flask
from backend.storage.neo4j_storage import Neo4jStorage
import os

def create_app():
    app = Flask(__name__)

    # 单个存储实例,通过 app.extensions 注入到各处
    storage = Neo4jStorage(
        uri=os.environ["NEO4J_URI"],
        user=os.environ["NEO4J_USER"],
        password=os.environ["NEO4J_PASSWORD"],
        embedding_model=os.environ["EMBEDDING_MODEL"],
        embedding_base_url=os.environ["EMBEDDING_BASE_URL"],
        llm_base_url=os.environ["LLM_BASE_URL"],
        llm_api_key=os.environ["LLM_API_KEY"],
        llm_model=os.environ["LLM_MODEL_NAME"],
    )
    app.extensions["neo4j_storage"] = storage

    from backend.routes import graph_bp, simulation_bp, report_bp
    app.register_blueprint(graph_bp)
    app.register_blueprint(simulation_bp)
    app.register_blueprint(report_bp)

    return app

在 Flask 路由中访问存储

from flask import Blueprint, current_app, request, jsonify

simulation_bp = Blueprint("simulation", __name__)

@simulation_bp.route("/api/simulation/run", methods=["POST"])
def run_simulation():
    storage = current_app.extensions["neo4j_storage"]
    data = request.json

    sim = SimulationService(storage=storage)
    sim_id = sim.create_environment(
        graph_id=data["graph_id"],
        agent_count=data.get("agent_count", 200),
        simulation_hours=data.get("simulation_hours", 24),
    )
    result = sim.run(sim_id=sim_id)
    return jsonify(result)

REST API 参考

方法 端点 描述
POST /api/graph/build 上传文档,构建知识图谱
GET /api/graph/:id 获取图谱实体和关系
POST /api/simulation/create 创建模拟环境
POST /api/simulation/run 执行模拟
GET /api/simulation/:id/results 获取帖子、情感、指标
GET /api/simulation/:id/agents 列出生成的智能体
POST /api/report/generate 生成 ReportAgent 分析
POST /api/agent/:id/chat 与特定智能体聊天
GET /api/search 对知识图谱进行混合搜索

示例:从文档构建图谱

curl -X POST http://localhost:5000/api/graph/build \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Acme Corp 宣布创纪录的第四季度收益,CFO 辞职...",
    "title": "Q4 新闻稿",
    "source_type": "press_release"
  }'
# → {"graph_id": "g_abc123", "entities": 47, "relationships": 89}

示例:运行模拟

curl -X POST http://localhost:5000/api/simulation/run \
  -H "Content-Type: application/json" \
  -d '{
    "graph_id": "g_abc123",
    "agent_count": 150,
    "simulation_hours": 12,
    "platform": "twitter"
  }'
# → {"sim_id": "s_xyz789", "status": "running"}

硬件选择指南

使用场景 模型 显存 内存
快速测试 / 开发 qwen2.5:7b 6 GB 16 GB
平衡质量 qwen2.5:14b 10 GB 16 GB
生产质量 qwen2.5:32b 24 GB 32 GB
仅 CPU(慢) qwen2.5:7b 16 GB

通过编辑 .env 切换模型:

LLM_MODEL_NAME=qwen2.5:14b

然后重启后端 — 无需其他更改。


常见模式

公关危机测试流程

import os
from backend.storage.neo4j_storage import Neo4jStorage
from backend.services.graph_builder import GraphBuilder
from backend.services.simulation import SimulationService
from backend.services.report import ReportAgent

storage = Neo4jStorage(
    uri=os.environ["NEO4J_URI"],
    user=os.environ["NEO4J_USER"],
    password=os.environ["NEO4J_PASSWORD"],
    embedding_model=os.environ["EMBEDDING_MODEL"],
    embedding_base_url=os.environ["EMBEDDING_BASE_URL"],
    llm_base_url=os.environ["LLM_BASE_URL"],
    llm_api_key=os.environ["LLM_API_KEY"],
    llm_model=os.environ["LLM_MODEL_NAME"],
)

def test_press_release(text: str) -> dict:
    # 1. 构建知识图谱
    builder = GraphBuilder(storage=storage)
    graph_id = builder.build(content=text, title="草案", source_type="press_release")

    # 2. 模拟公众反应
    sim = SimulationService(storage=storage)
    sim_id = sim.create_environment(graph_id=graph_id, agent_count=300, simulation_hours=48)
    sim.run(sim_id=sim_id)

    # 3. 生成报告
    report = ReportAgent(storage=storage).generate(sim_id=sim_id, focus_group_size=15)

    return {
        "sentiment_peak": report["sentiment_over_time"][0],
        "key_narratives": report["key_narratives"],
        "risk_score": report["risk_score"],
        "recommended_edits": report["recommendations"],
    }

# 用法
with open("draft_announcement.txt") as f:
    result = test_press_release(f.read())

print(f"风险评分:{result['risk_score']}/10")
print(f"主要叙事:{result['key_narratives'][0]}")

使用任何 OpenAI 兼容的提供商

# 通过 Anthropic 使用 Claude(或任何代理)
LLM_API_KEY=$ANTHROPIC_API_KEY
LLM_BASE_URL=https://api.anthropic.com/v1
LLM_MODEL_NAME=claude-3-5-sonnet-20241022

# OpenAI
LLM_API_KEY=$OPENAI_API_KEY
LLM_BASE_URL=https://api.openai.com/v1
LLM_MODEL_NAME=gpt-4o

# 本地 LM Studio
LLM_API_KEY=lm-studio
LLM_BASE_URL=http://localhost:1234/v1
LLM_MODEL_NAME=your-loaded-model

故障排除

Neo4j 连接被拒绝

# 检查 Neo4j 是否正在运行
docker ps | grep neo4j
# 检查 bolt 端口
nc -zv localhost 7687
# 查看 Neo4j 日志
docker logs neo4j --tail 50

Ollama 找不到模型

# 列出可用模型
ollama list
# 拉取缺失的模型
ollama pull qwen2.5:32b
ollama pull nomic-embed-text
# 检查 Ollama 是否正在服务
curl http://localhost:11434/api/tags

显存不足

# 在 .env 中切换到更小的模型
LLM_MODEL_NAME=qwen2.5:14b   # 或 qwen2.5:7b
# 重启后端
cd backend && python run.py

嵌入向量维度不匹配

# nomic-embed-text 生成 768 维向量
# 如果切换嵌入模型,请删除并重新创建 Neo4j 向量索引:
# 在 Neo4j 浏览器中 (http://localhost:7474):
# DROP INDEX entity_embedding IF EXISTS;
# 然后重启 MiroFish — 它会以正确的维度重新创建索引。

Docker Compose:Ollama 容器无法访问 GPU

# docker-compose.yml — 添加 GPU 预留:
services:
  ollama:
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

CPU 上模拟速度慢

  • 使用 qwen2.5:7b 进行更快的(质量较低)推理
  • agent_count 减少到 50–100 用于测试
  • simulation_hours 减少到 6–12
  • 使用 7b 模型的 CPU 推理:预计约 5–10 tokens/秒

前端无法访问后端

# 检查 frontend/.env 中的 VITE_API_BASE_URL
VITE_API_BASE_URL=http://localhost:5000

# 验证后端是否已启动
curl http://localhost:5000/api/health

项目结构

MiroFish-Offline/
├── backend/
│   ├── run.py                    # 入口点
│   ├── app.py                    # Flask 工厂,依赖注入
│   ├── storage/
│   │   ├── base.py               # GraphStorage 抽象接口
│   │   └── neo4j_storage.py      # Neo4j 实现
│   ├── services/
│   │   ├── graph_builder.py      # 命名实体识别 + 关系提取
│   │   ├── simulation.py         # 智能体模拟引擎
│   │   ├── report.py             # ReportAgent + 焦点小组
│   │   ├── agent_chat.py         # 每个智能体的聊天接口
│   │   └── search.py             # 混合向量 + BM25 搜索
│   └── routes/
│       ├── graph.py
│       ├── simulation.py
│       └── report.py
├── frontend/                     # Vue 3(完全英文 UI)
├── docker-compose.yml
├── .env.example
└── README.md

📄 原始文档

完整文档(英文):

https://skills.sh/aradotso/trending-skills/mirofish-offline-simulation

💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。