Skill: saybanet/mlx-local-ai (version 1.0.1)

# MLX Local AI 技能 一键部署本地 AI 服务，支持 Apple Silicon (M1/M2/M3/M4)。 ## 功能 - 🤖 **本地 LLM 服务**：Qwen3.5-4B-OptiQ-4bit (通过 MLX-LM) - 📊 **Embedding 服务**：bge-base-zh-v1.5 (中文向量模型) - 🔄 **OpenClaw Gateway**：统一 API 网关 - 🎯 **一键安装/启动/停止** ## 系统要求 - macOS 14.0+ (Sonoma 或更高) - Apple Silicon (M1/M2/M3/M4) - Python 3.10+ - 8GB+ 内存 ## 快速开始 ### 安装 ```bash ./install.sh ``` ### 启动服务 ```bash ./start_ai.sh start ``` ### 检查状态 ```bash ./start_ai.sh status ``` ### 停止服务 ```bash ./start_ai.sh stop ``` ## 服务端口 | 服务 | 端口 | 说明 | |------|------|------| | Chat API | 8080 | OpenAI 兼容 API | | Embedding API | 8081 | 向量嵌入 API | | OpenClaw Gateway | 18789 | 管理界面 | ## API 使用 ### Chat API ```bash curl http://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "mlx-community/Qwen3.5-4B-OptiQ-4bit", "messages": [{"role": "user", "content": "你好"}], "max_tokens": 100 }' ``` ### Embedding API ```bash curl http://localhost:8081/embed \ -H "Content-Type: application/json" \ -d '{"texts": ["你好", "世界"]}' ``` ## 文件结构 ``` mlx-local-ai/ ├── SKILL.md # 本文件 ├── install.sh # 安装脚本 ├── start_ai.sh # 启动脚本 ├── uninstall.sh # 卸载脚本 ├── config/ # 配置文件 │ ├── openclaw.json # OpenClaw 配置 │ └── env.example # 环境变量示例 └── scripts/ # 工具脚本 ├── check_deps.py # 依赖检查 └── test_api.py # API 测试 ``` ## 配置 ### 环境变量 复制 `config/env.example` 到 `~/.zshrc`： ```bash cp config/env.example ~/.zshrc.local echo 'source ~/.zshrc.local' >> ~/.zshrc ``` ### 模型配置 默认使用 `mlx-community/Qwen3.5-4B-OptiQ-4bit`，可在 `start_ai.sh` 中修改。 ## 故障排查 ### 服务无法启动 ```bash # 检查依赖 python3 scripts/check_deps.py # 查看日志 ./start_ai.sh logs ``` ### 模型加载失败 ```bash # 手动下载模型 source ~/mlx-env/bin/activate HF_ENDPOINT=https://hf-mirror.com python3 -c "from mlx_lm import load; load('mlx-community/Qwen3.5-4B-OptiQ-4bit')" ``` ## 更新 ```bash ./install.sh --update ``` ## 卸载 ```bash ./uninstall.sh ``` ## 许可证 MIT License

path: start_ai.sh
content: #!/bin/bash

# AI 服务管理脚本
# 用法: start_ai.sh [start|stop|status|restart]

export HF_ENDPOINT=https://hf-mirror.com
LOG_DIR=~/.openclaw/logs
mkdir -p "$LOG_DIR"

check_embedding() {
    curl -s http://localhost:8081/health > /dev/null 2>&1
}

check_chat() {
    curl -s http://localhost:8080/v1/models > /dev/null 2>&1
}

start_services() {
    echo "🚀 启动 AI 服务..."
    source ~/mlx-env/bin/activate
    
    # Embedding 服务
    if check_embedding; then
        echo "✅ Embedding 服务已运行 (端口 8081)"
    else
        echo "📊 启动 Embedding 服务..."
        nohup python ~/embedding_server.py > "$LOG_DIR/embedding.log" 2>&1 &
        sleep 3
        if check_embedding; then
            echo "✅ Embedding 服务启动成功"
        else
            echo "❌ Embedding 服务启动失败"
        fi
    fi
    
    # Chat 服务
    if check_chat; then
        echo "✅ Chat 服务已运行 (端口 8080)"
    else
        echo "💬 启动 Chat 服务 (mlx-community/Qwen3.5-4B-OptiQ-4bit)..."
        nohup python -m mlx_lm.server \
            --model mlx-community/Qwen3.5-4B-OptiQ-4bit \
            --trust-remote-code \
            --temp 0.3 \
            --chat-template-args '{"enable_thinking": false}' \
            --port 8080 > "$LOG_DIR/chat.log" 2>&1 &
        sleep 5
        if check_chat; then
            echo "✅ Chat 服务启动成功"
        else
            echo "❌ Chat 服务启动失败，查看日志: $LOG_DIR/chat.log"
        fi
    fi
}

stop_services() {
    echo "🛑 停止 AI 服务..."
    
    # 停止 Chat 服务
    if pgrep -f "mlx_lm.server" > /dev/null; then
        pkill -f "mlx_lm.server"
        echo "✅ Chat 服务已停止"
    else
        echo "ℹ️  Chat 服务未运行"
    fi
    
    # 停止 Embedding 服务
    if pgrep -f "embedding_server.py" > /dev/null; then
        pkill -f "embedding_server.py"
        echo "✅ Embedding 服务已停止"
    else
        echo "ℹ️  Embedding 服务未运行"
    fi
}

show_status() {
    echo "📊 AI 服务状态"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    
    # Embedding 服务
    if check_embedding; then
        INFO=$(curl -s http://localhost:8081/health 2>/dev/null)
        MEM_COUNT=$(echo "$INFO" | jq -r '.memory_count // "N/A"' 2>/dev/null)
        echo "Embedding (8081): ✅ 运行中 ($MEM_COUNT 条记忆)"
    else
        echo "Embedding (8081): ❌ 未运行"
    fi
    
    # Chat 服务
    if check_chat; then
        MODEL=$(curl -s http://localhost:8080/v1/models 2>/dev/null | jq -r '.data[0].id // "unknown"' 2>/dev/null)
        echo "Chat (8080):      ✅ 运行中 ($MODEL)"
    else
        echo "Chat (8080):      ❌ 未运行"
    fi
    
    echo ""
    echo "日志目录: $LOG_DIR"
}

case "${1:-}" in
    start)
        start_services
        ;;
    stop)
        stop_services
        ;;
    status)
        show_status
        ;;
    restart)
        stop_services
        sleep 2
        start_services
        ;;
    "")
        start_services
        ;;
    *)
        echo "用法: $0 [start|stop|status|restart]"
        echo ""
        echo "  start   - 启动所有服务 (默认)"
        echo "  stop    - 停止所有服务"
        echo "  status  - 查看服务状态"
        echo "  restart - 重启所有服务"
        exit 1
        ;;
esac


path: uninstall.sh
content: #!/bin/bash

# MLX Local AI 卸载脚本

set -e

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

VENV_DIR="$HOME/mlx-env"
LOG_DIR="$HOME/.openclaw/logs"

echo ""
echo "========================================"
echo "   MLX Local AI 卸载脚本"
echo "========================================"
echo ""

read -p "确定要卸载 MLX Local AI? (y/N) " -n 1 -r
echo

if [[ ! $REPLY =~ ^[Yy]$ ]]; then
    echo "取消卸载"
    exit 0
fi

echo -e "${YELLOW}停止服务...${NC}"
~/start_ai.sh stop 2>/dev/null || true

echo -e "${YELLOW}删除虚拟环境...${NC}"
rm -rf "$VENV_DIR"

echo -e "${YELLOW}删除启动脚本...${NC}"
rm -f "$HOME/start_ai.sh"

echo -e "${YELLOW}删除日志文件...${NC}"
rm -rf "$LOG_DIR"

echo ""
echo -e "${GREEN}✅ 卸载完成${NC}"
echo ""
echo "注意: 模型缓存未删除，如需删除请运行:"
echo "  rm -rf ~/.cache/huggingface/hub/models--mlx-community--Qwen3.5-4B-OptiQ-4bit"
echo "  rm -rf ~/.cache/huggingface/hub/models--BAAI--bge-base-zh-v1.5"


path: config/openclaw.json
content: {
  "models": {
    "providers": {
      "mlx-local": {
        "baseUrl": "http://localhost:8080/v1",
        "apiKey": "local-mlx",
        "api": "openai-completions",
        "models": [
          {
            "id": "mlx-community/Qwen3.5-4B-OptiQ-4bit",
            "name": "Local Qwen3.5 4B (MLX)",
            "reasoning": false,
            "input": ["text"],
            "cost": {
              "input": 0,
              "output": 0,
              "cacheRead": 0,
              "cacheWrite": 0
            },
            "contextWindow": 128000,
            "maxTokens": 8192
          }
        ]
      }
    }
  },
  "agents": {
    "defaults": {
      "model": {
        "primary": "baiduqianfancodingplan/qianfan-code-latest",
        "fallbacks": [
          "mlx-local/mlx-community/Qwen3.5-4B-OptiQ-4bit"
        ]
      },
      "models": {
        "mlx-local/mlx-community/Qwen3.5-4B-OptiQ-4bit": {
          "alias": "Qwen3.5-4B-MLX"
        }
      }
    }
  }
}


path: install.sh
content: #!/bin/bash

# MLX Local AI 安装脚本
# 支持 macOS Apple Silicon (M1/M2/M3/M4)

set -e

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 配置
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VENV_DIR="$HOME/mlx-env"
MODEL_NAME="mlx-community/Qwen3.5-4B-OptiQ-4bit"
EMBEDDING_MODEL="BAAI/bge-base-zh-v1.5"
HF_MIRROR="https://hf-mirror.com"

# 日志函数
log_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 检查系统
check_system() {
    log_info "检查系统..."
    
    # 检查 macOS
    if [[ "$(uname)" != "Darwin" ]]; then
        log_error "此脚本仅支持 macOS"
        exit 1
    fi
    
    # 检查 Apple Silicon
    ARCH=$(uname -m)
    if [[ "$ARCH" != "arm64" ]]; then
        log_error "此脚本仅支持 Apple Silicon (M1/M2/M3/M4)"
        exit 1
    fi
    
    log_success "系统检测通过: macOS arm64"
}

# 检查依赖
check_dependencies() {
    log_info "检查依赖..."
    
    # 检查 Python
    if ! command -v python3 &> /dev/null; then
        log_error "Python3 未安装，请先安装 Python"
        log_info "推荐使用 Homebrew: brew install python@3.11"
        exit 1
    fi
    
    PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}')
    log_success "Python 版本: $PYTHON_VERSION"
    
    # 检查 pip
    if ! command -v pip3 &> /dev/null; then
        log_error "pip3 未安装"
        exit 1
    fi
    
    # 检查 Homebrew (可选)
    if command -v brew &> /dev/null; then
        log_success "Homebrew 已安装"
    else
        log_warning "Homebrew 未安装 (可选)"
    fi
}

# 创建虚拟环境
create_venv() {
    log_info "创建 Python 虚拟环境..."
    
    if [[ -d "$VENV_DIR" ]]; then
        log_warning "虚拟环境已存在: $VENV_DIR"
        read -p "是否重新创建? (y/N) " -n 1 -r
        echo
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            rm -rf "$VENV_DIR"
        else
            log_info "跳过虚拟环境创建"
            return
        fi
    fi
    
    python3 -m venv "$VENV_DIR"
    log_success "虚拟环境创建完成: $VENV_DIR"
}

# 安装 Python 依赖
install_dependencies() {
    log_info "安装 Python 依赖..."
    
    source "$VENV_DIR/bin/activate"
    
    # 升级 pip
    pip install --upgrade pip
    
    # 安装 MLX 和 MLX-LM
    log_info "安装 MLX 和 MLX-LM..."
    pip install mlx mlx-lm
    
    # 安装 sentence-transformers (用于 Embedding)
    log_info "安装 sentence-transformers..."
    pip install sentence-transformers
    
    # 安装其他依赖
    pip install flask requests numpy
    
    log_success "Python 依赖安装完成"
}

# 下载模型
download_models() {
    log_info "下载模型 (这可能需要几分钟)..."
    
    source "$VENV_DIR/bin/activate"
    export HF_ENDPOINT="$HF_MIRROR"
    
    # 下载 LLM 模型
    log_info "下载 LLM 模型: $MODEL_NAME"
    python3 -c "from mlx_lm import load; load('$MODEL_NAME')" || {
        log_warning "LLM 模型下载可能已存在或失败，继续..."
    }
    
    # 下载 Embedding 模型
    log_info "下载 Embedding 模型: $EMBEDDING_MODEL"
    python3 -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('$EMBEDDING_MODEL')" || {
        log_warning "Embedding 模型下载可能已存在或失败，继续..."
    }
    
    log_success "模型下载完成"
}

# 创建配置文件
create_config() {
    log_info "创建配置文件..."
    
    # 创建日志目录
    mkdir -p "$HOME/.openclaw/logs"
    
    # 创建环境变量文件
    cat > "$SCRIPT_DIR/config/env.example" << EOF
# MLX Local AI 环境变量
export HF_ENDPOINT="$HF_MIRROR"

# 百度 API (可选)
# export BAIDU_API_KEY="your-api-key"

# Tavily API (可选)
# export TAVILY_API_KEY="your-api-key"
EOF
    
    log_success "配置文件创建完成"
}

# 创建启动脚本
create_start_script() {
    log_info "创建启动脚本..."
    
    cp "$SCRIPT_DIR/start_ai.sh" "$HOME/start_ai.sh"
    chmod +x "$HOME/start_ai.sh"
    log_success "启动脚本创建完成: ~/start_ai.sh"
}

# 复制 embedding_server.py
create_embedding_server() {
    log_info "创建 Embedding 服务..."
    
    if [[ ! -f "$HOME/embedding_server.py" ]]; then
        # 从 scripts 目录复制或创建默认版本
        if [[ -f "$SCRIPT_DIR/scripts/embedding_server.py" ]]; then
            cp "$SCRIPT_DIR/scripts/embedding_server.py" "$HOME/embedding_server.py"
        else
            log_warning "embedding_server.py 不存在，请手动创建"
        fi
    fi
}

# 主安装流程
main() {
    echo ""
    echo "========================================"
    echo "   MLX Local AI 安装脚本"
    echo "========================================"
    echo ""
    
    check_system
    check_dependencies
    create_venv
    install_dependencies
    download_models
    create_config
    create_start_script
    create_embedding_server
    
    echo ""
    echo "========================================"
    echo -e "${GREEN}✅ 安装完成！${NC}"
    echo "========================================"
    echo ""
    echo "启动服务:"
    echo "  ~/start_ai.sh start"
    echo ""
    echo "检查状态:"
    echo "  ~/start_ai.sh status"
    echo ""
    echo "测试 API:"
    echo "  ~/start_ai.sh test"
    echo ""
}

# 处理命令行参数
case "${1:-}" in
    --update)
        log_info "更新模式..."
        install_dependencies
        log_success "更新完成"
        ;;
    --uninstall)
        log_info "卸载..."
        rm -rf "$VENV_DIR"
        rm -f "$HOME/start_ai.sh"
        rm -f "$HOME/embedding_server.py"
        log_success "卸载完成"
        ;;
    *)
        main
        ;;
esac


path: README.md
content: # MLX Local AI

一键部署本地 AI 服务，支持 Apple Silicon (M1/M2/M3/M4)。

## 功能

- 🤖 **本地 LLM 服务**：Qwen3.5-4B-OptiQ-4bit
- 📊 **Embedding 服务**：bge-base-zh-v1.5
- 🔄 **OpenClaw Gateway**：统一 API 网关
- 🎯 **一键安装/启动/停止**

## 快速开始

```bash
# 1. 安装
./install.sh

# 2. 启动服务
~/start_ai.sh start

# 3. 检查状态
~/start_ai.sh status

# 4. 测试 API
~/start_ai.sh test
```

## 文件结构

```
mlx-local-ai/
├── SKILL.md           # 技能说明
├── README.md          # 本文件
├── install.sh         # 安装脚本
├── uninstall.sh       # 卸载脚本
├── config/            # 配置文件
│   ├── openclaw.json  # OpenClaw 配置
│   └── env.example    # 环境变量示例
└── scripts/           # 工具脚本
    ├── check_deps.py  # 依赖检查
    └── test_api.py    # API 测试
```

## 服务端口

| 服务 | 端口 | 说明 |
|------|------|------|
| Chat API | 8080 | OpenAI 兼容 API |
| Embedding API | 8081 | 向量嵌入 API |

## 用于 ClawHub

此技能可发布到 ClawHub 供其他用户安装使用。

### 发布命令

```bash
clawhub publish mlx-local-ai
```

### 安装命令

```bash
clawhub install mlx-local-ai --dir ~/.openclaw/skills
```

## 许可证

MIT License


path: scripts/check_deps.py
content: #!/usr/bin/env python3
"""
MLX Local AI 依赖检查脚本
"""

import sys
import subprocess

def check_python_version():
    """检查 Python 版本"""
    version = sys.version_info
    print(f"Python 版本: {version.major}.{version.minor}.{version.micro}")
    if version.major < 3 or (version.major == 3 and version.minor < 10):
        print("❌ Python 版本过低，需要 3.10+")
        return False
    print("✓ Python 版本符合要求")
    return True

def check_architecture():
    """检查系统架构"""
    import platform
    arch = platform.machine()
    print(f"系统架构: {arch}")
    if arch != "arm64":
        print("❌ 此脚本仅支持 Apple Silicon (arm64)")
        return False
    print("✓ 系统架构符合要求")
    return True

def check_package(package_name):
    """检查 Python 包是否安装"""
    try:
        __import__(package_name)
        print(f"✓ {package_name} 已安装")
        return True
    except ImportError:
        print(f"❌ {package_name} 未安装")
        return False

def check_mlx():
    """检查 MLX 是否可用"""
    try:
        import mlx.core as mx
        print(f"✓ MLX 版本: {mx.__version__}")
        
        # 检查 Metal
        if mx.metal.is_available():
            print("✓ Metal GPU 可用")
        else:
            print("⚠️ Metal GPU 不可用")
        return True
    except ImportError:
        print("❌ MLX 未安装")
        return False

def main():
    print("=" * 50)
    print("MLX Local AI 依赖检查")
    print("=" * 50)
    print()
    
    checks = [
        ("Python 版本", check_python_version),
        ("系统架构", check_architecture),
    ]
    
    packages = [
        "mlx",
        "mlx_lm",
        "sentence_transformers",
        "flask",
        "numpy",
        "requests",
    ]
    
    print("【系统检查】")
    for name, check in checks:
        check()
        print()
    
    print("【包检查】")
    for pkg in packages:
        check_package(pkg)
    
    print()
    print("【MLX 详细检查】")
    check_mlx()
    
    print()
    print("=" * 50)

if __name__ == "__main__":
    main()


path: scripts/test_api.py
content: #!/usr/bin/env python3
"""
MLX Local AI API 测试脚本
"""

import json
import urllib.request
import urllib.error

CHAT_URL = "http://localhost:8080/v1/chat/completions"
EMBEDDING_URL = "http://localhost:8081"

def test_chat_api():
    """测试 Chat API"""
    print("【测试 Chat API】")
    
    data = {
        "model": "mlx-community/Qwen3.5-4B-OptiQ-4bit",
        "messages": [{"role": "user", "content": "你好，请简短自我介绍"}],
        "max_tokens": 50
    }
    
    try:
        req = urllib.request.Request(
            CHAT_URL,
            data=json.dumps(data).encode('utf-8'),
            headers={"Content-Type": "application/json"},
            method='POST'
        )
        
        with urllib.request.urlopen(req, timeout=60) as response:
            result = json.loads(response.read().decode('utf-8'))
            content = result['choices'][0]['message']['content']
            print(f"✓ Chat API 正常")
            print(f"  回复: {content[:100]}...")
            return True
    except Exception as e:
        print(f"❌ Chat API 错误: {e}")
        return False

def test_embedding_api():
    """测试 Embedding API"""
    print("\n【测试 Embedding API】")
    
    data = {"texts": ["你好", "世界"]}
    
    try:
        req = urllib.request.Request(
            f"{EMBEDDING_URL}/embed",
            data=json.dumps(data).encode('utf-8'),
            headers={"Content-Type": "application/json"},
            method='POST'
        )
        
        with urllib.request.urlopen(req, timeout=30) as response:
            result = json.loads(response.read().decode('utf-8'))
            embeddings = result['embeddings']
            print(f"✓ Embedding API 正常")
            print(f"  向量维度: {len(embeddings[0])}")
            return True
    except Exception as e:
        print(f"❌ Embedding API 错误: {e}")
        return False

def test_health():
    """测试健康检查"""
    print("\n【测试健康检查】")
    
    try:
        req = urllib.request.Request(f"{EMBEDDING_URL}/health", method='GET')
        with urllib.request.urlopen(req, timeout=5) as response:
            result = json.loads(response.read().decode('utf-8'))
            print(f"✓ Embedding 服务健康: {result['status']}")
            return True
    except Exception as e:
        print(f"❌ 健康检查失败: {e}")
        return False

def main():
    print("=" * 50)
    print("MLX Local AI API 测试")
    print("=" * 50)
    print()
    
    results = []
    results.append(("Chat API", test_chat_api()))
    results.append(("Embedding API", test_embedding_api()))
    results.append(("健康检查", test_health()))
    
    print("\n" + "=" * 50)
    print("测试结果:")
    for name, passed in results:
        status = "✓ 通过" if passed else "❌ 失败"
        print(f"  {name}: {status}")
    print("=" * 50)

if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)