云计算百科
云计算领域专业知识百科平台

使用 Python 调用 Apple 端侧 Foundation Model:apfel 本地 OpenAI 兼容服务器实战指南

【免费下载链接】apfel

The free AI already on your Mac. CLI tool, OpenAI-compatible server, and interactive chat — all on-device via Apple Intelligence. No API keys, no cloud, no downloads.

项目地址:
https://gitcode.com/gh_mirrors/apfe/apfel

点击查看 免费下载

导读:本文以 docs/guides/python.md 为核心,完整讲解如何用官方 openai Python SDK 对接本地运行的 apfel –serve,让 Apple 端侧 Foundation Model 通过 OpenAI 兼容的 http://localhost:11434/v1 接口为 AI/ML 脚本、数据工具和后端服务提供推理能力——100% 端侧执行、零 API 费用、推理过程无需联网。读完本文,你将掌握一次性对话补全、流式输出、JSON 结构化输出、错误处理、工具调用(function calling)以及"从 stdin 读取文件做摘要"这六种开箱即用的实战模式,并了解每条模式背后在 apfel 仓库中的实现依据。

运行前提:软硬件与服务器准备

按照 docs/guides/python.md 的要求,使用前需要满足以下条件:

前提说明
macOS 26+ Tahoe、Apple Silicon 端侧 Foundation Model 依赖 Apple Intelligence 框架
Apple Intelligence 已开启 System Settings -> Apple Intelligence & Siri
brew install apfel 安装 apfel CLI
apfel –serve 正在运行 默认端口 11434
Python 3.11+ SDK 运行环境
pip install openai(或 uv add openai) OpenAI 官方 Python SDK

apfel 的 –serve 模式在源码层面有明确佐证:Sources/CLI/CLIArguments.swift 中 serverPort 默认值为 11434、serverHost 默认值为 127.0.0.1,因此 SDK 指向 http://localhost:11434/v1 即可。同时该文件显示端口可通过 –port 参数或 APFEL_PORT 环境变量覆盖(非法值会被忽略并记录警告,而不是静默生效),例如:

apfel –serve –port 3000 –host 0.0.0.0 –cors

服务器启动后,建议先做一次健康检查:

apfel –serve &
curl -s http://localhost:11434/health
# {"status":"ok"}

/health 端点由 Sources/Server.swift 实现,除 status 外还会返回 model、version、context_window、context_window_measured、capabilities、supported_languages 等字段;/v1/models 则会列出 apple-foundationmodel 及其支持的参数清单(见下文"能力边界")。

1. 一次性对话补全(One-shot chat completion)

把 openai SDK 的 base_url 指向本地 apfel 服务器,即可调用 chat.completions.create:

from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="not-needed")

response = client.chat.completions.create(
model="apple-foundationmodel",
messages=[
{"role": "user", "content": "In one sentence, what is the Swift programming language?"},
],
max_tokens=80,
)

print((response.choices[0].message.content or "").strip())

真实输出:

Swift is a modern, high-performance, and safe programming language developed by Apple for developing iOS, macOS, watchOS, and tvOS applications.

几点说明:

  • api_key="not-needed" 是占位值:apfel 默认不校验 Authorization 头。只有当服务器以 –token 启动(或用 APFEL_TOKEN 环境变量)时,SDK 才需要传入真实令牌,具体见 docs/server-security.md。
  • model 固定为 apple-foundationmodel,与 /v1/models 返回的模型 ID 一致(见 Sources/Server.swift)。
  • 省略 max_tokens 时,apfel 采用 OpenAI 语义"使用剩余上下文窗口";若显式传入,则按你的预算截断,触顶时返回 finish_reason: "length" 和部分内容(HTTP 200)。完整参数映射见 docs/openai-api-compatibility.md。
  • 仓库集成测试 Tests/integration/openai_client_test.py 中的 test_basic_completion、test_system_prompt、test_multi_turn_history 验证了非流式补全、系统提示词与多轮历史场景。

2. 流式输出(Streaming)

传入 stream=True 并迭代 chunk。注意:当启用 stream_options: {"include_usage": true} 时,最终会多出一个 choices 为空的 usage chunk,因此代码必须对空 choices 做防护:

import sys
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="not-needed")

stream = client.chat.completions.create(
model="apple-foundationmodel",
messages=[{"role": "user", "content": "List three Apple silicon chips, one per line."}],
max_tokens=80,
stream=True,
)

for chunk in stream:
if not chunk.choices:
continue
delta = chunk.choices[0].delta.content or ""
sys.stdout.write(delta)
sys.stdout.flush()
print()

真实输出:

Apple M1
Apple M2
Apple M3

从协议实现看,流式响应以 SSE(Server-Sent Events)传输,finish_reason 支持 stop、tool_calls、length;stream_options.include_usage 为可选开关,开启后才会在 [DONE] 之前发送空 choices 的 usage chunk(见 docs/openai-api-compatibility.md 的兼容性表格)。SDK 侧的编解码在 Tests/apfelTests/OpenAIWireFormatTests.swift 中有对应断言(stream、stream_options.include_usage、response_format 均可正确解析),集成测试 Tests/integration/openai_client_test.py 的 test_streaming 也覆盖了该流程。

3. JSON 模式 / 结构化输出

请求 response_format: {"type": "json_object"} 并按 JSON 解析。一个常见坑是:apfel 偶尔会把 JSON 包在 markdown 代码围栏里,因此文档给出了一行围栏剥离正则,两种情形都能干净处理:

import json, re
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="not-needed")

response = client.chat.completions.create(
model="apple-foundationmodel",
messages=[{
"role": "user",
"content": "Return JSON with fields 'chip', 'year', 'cores'. Describe the Apple M1 chip. Return ONLY JSON.",
}],
response_format={"type": "json_object"},
max_tokens=120,
)

raw = (response.choices[0].message.content or "").strip()
raw = re.sub(r"^```(?:json)?\\s*|\\s*```$", "", raw, flags=re.MULTILINE).strip()
data = json.loads(raw)
print(json.dumps(data, indent=2, sort_keys=True))

真实输出:

{
"chip": "Apple M1",
"cores": 8,
"year": 2020
}

关于围栏剥离,仓库里其实内置了比这个正则更精细的实现:Sources/Core/JSONFenceStripper.swift 中的 JSONFenceStripper.strip(_:) 只剥离"以三反引号开头且围栏标签为空或为 json"的块,而不会误伤 ```python … ``` 这类代码块;OpenAI 规范要求 json_object 模式下 message content 必须是合法 JSON,因此服务端会先做后处理再返回原始 JSON。也就是说:即便你在客户端不做任何剥离,apfel 返回的 content 也已经是干净 JSON;文档保留这行正则是为了兼容更早版本或直接透传的原始流。另外,若需要更强约束的结构化输出(保证符合给定 JSON Schema),apfel 还支持 response_format: {"type": "json_schema", …} 以及 CLI 侧 apfel –schema file.json,具体支持/拒绝的关键字清单见 docs/openai-api-compatibility.md。

4. 错误处理:apfel 对不支持的能力返回诚实 HTTP 错误

apfel 对未实现的能力不会假装成功,而是返回明确的 HTTP 错误码。典型例子是 embeddings 接口返回 501:

from openai import APIStatusError, OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="not-needed")

try:
client.embeddings.create(
model="apple-foundationmodel",
input="apfel runs 100% on-device.",
)
except APIStatusError as e:
print(f"Got expected error: HTTP {e.status_code} – {e.message}")

真实输出:

Got expected error: HTTP 501 – Error code: 501 – {'error': {'message': "Embeddings not supported by Apple's on-device model.", 'type': 'invalid_request_error'}}

这条 501 由 Sources/Server.swift 直接返回,错误消息与文档截图完全一致;同文件还把旧版 /v1/completions(文本补全)也定为 501。结合 docs/openai-api-compatibility.md 的兼容性表格,apfel 的能力边界可以归纳为:

  • 不支持:embeddings(501,端侧模型没有向量接口,可用 sentence-transformers 等外部 embedder 补充)、视觉/音频(macOS 26 上图片输入返回 400)、logprobs=true、n>1、stop、presence_penalty、frequency_penalty(这些显式返回 400;n=1 与 logprobs=false 作为无操作接受)、Responses API 的服务端会话状态(previous_response_id、store 等为 501)。
  • 支持:GET /v1/models、GET /health、流式与非流式 /v1/chat/completions、/v1/responses 翻译层、工具调用、json_object/json_schema、temperature/top_p/max_tokens/seed、上下文策略扩展字段等。

/v1/models 响应中的 unsupported_parameters 字段(logprobs, n, stop, presence_penalty, frequency_penalty)就是为客户端做能力探测准备的,集成测试 Tests/integration/openai_client_test.py 也针对 /health 与模型能力做了断言。

5. 工具调用(Tool calling / function calling)

定义一个工具 schema,发送提示词,处理模型返回的 tool call,把结果以 role: "tool" 回填,再发起第二轮请求拿到最终回答——这是标准的 OpenAI 工具调用往返:

import json
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="not-needed")

TOOLS = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current temperature in Celsius for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string", "description": "City name"}},
"required": ["city"],
},
},
}]

def get_weather(city: str, **_: object) -> str:
fake = {"Vienna": 14, "Cupertino": 19, "Tokyo": 11}
return json.dumps({"city": city, "temp_c": fake.get(city, 15)})

messages = [{"role": "user", "content": "What is the temperature in Vienna right now?"}]

first = client.chat.completions.create(
model="apple-foundationmodel", messages=messages, tools=TOOLS, max_tokens=256,
)

msg = first.choices[0].message
messages.append(msg.model_dump(exclude_none=True))

if msg.tool_calls:
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = get_weather(**args)
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})

final = client.chat.completions.create(
model="apple-foundationmodel", messages=messages, max_tokens=120,
)
print((final.choices[0].message.content or "").strip())

真实输出:

The current temperature in Vienna is 14°C.

从实现角度看,apfel 对 tool_choice(none/auto/required/{"type":"function",…})与 parallel_tool_calls 是在响应边界强制执行而非仅提示引导;macOS 27 上 auto/none 还会额外映射到运行时 ToolCallingMode。相关的工具调用指南见 docs/tool-calling-guide.md,流式路径下的工具调用防泄漏回归测试见集成测试 test_streaming_tool_call_no_content_leak(保证客户端绝不会收到原始工具中间内容)。另外 apfel 还支持 –mcp 挂载本地或远程 MCP 工具服务器(Sources/CLI/CLIArguments.swift 中 –mcp、–mcp-timeout、–mcp-token 均有对应实现),CLI 与服务器两侧可共用同一套工具执行管线。

6. 实战示例:从 stdin 读取文件并做摘要

把前五种模式组合成真实脚本:读取 stdin 文本 → 用系统提示词约束输出格式 → 打印单段摘要:

import sys
from openai import OpenAI

text = sys.stdin.read().strip()
if not text:
sys.exit("usage: cat file.txt | python 06_example.py")

client = OpenAI(base_url="http://localhost:11434/v1", api_key="not-needed")

response = client.chat.completions.create(
model="apple-foundationmodel",
messages=[
{"role": "system", "content": "You are a concise summarizer. Reply with one short paragraph."},
{"role": "user", "content": f"Summarize:\\n\\n{text}"},
],
max_tokens=150,
)
print((response.choices[0].message.content or "").strip())

运行方式:

cat README.md | python 06_example.py

真实输出(对一段关于 M1 芯片的段落做摘要):

The Apple M1 chip, released in November 2020, was Apple's first ARM-based system-on-a-chip for Mac computers. It features an 8-core CPU with four performance and four efficiency cores, plus an integrated GPU with up to 8 cores. The chip combines CPU, GPU, memory, and neural engine on a single die, delivering significant performance-per-watt improvements over the Intel chips it replaced.

这段脚本展示了端侧模型用于"本地文档快速摘要"的最小可用形态:把文件内容拼进 user 消息,配合 system 提示词约束风格,max_tokens 控制输出长度。同样的管道模式可以扩展为批量处理日志、代码评审草稿等本地任务,全程不出网。

故障排查(Troubleshooting)

现象原因与对策
11434 端口 Connection refused 服务器没启动。先运行 apfel –serve;若换了端口,SDK 的 base_url 要同步改
Embeddings not supported apfel 仅文本能力,向量请改用 sentence-transformers 等外部 embedder
JSON 模式下 JSONDecodeError 保留围栏剥离正则;apfel 有时会把 JSON 包在 ```json … ``` 里(服务端已内置剥离,见 Sources/Core/JSONFenceStripper.swift,客户端正则可作双保险)
流式输出为空 确认客户端处理了末尾空 choices 的 usage chunk,if not chunk.choices: continue 即可覆盖
模型拒绝工具调用 小尺寸端侧模型偶尔会拒绝执行工具,直接重试整次调用即可

版本与测试环境

文档记录该指南最初实测于以下环境,且后续每个版本都受 apfel 测试套件门禁保护(CLI 与 HTTP 表面行为稳定):

  • apfel v1.0.3(原始捕获版本;CLI 与 HTTP 接口由 apfel 测试套件在每个版本上把关)
  • macOS 26.3.1、Apple Silicon
  • Python 3.11 / openai 2.31.0
  • 日期:2026-04-16

仓库内可复现的等价测试包括:集成测试 Tests/integration/openai_client_test.py(基本补全、流式、多轮、系统提示词、工具调用、流式工具调用防泄漏等)与单元测试 Tests/apfelTests/OpenAIWireFormatTests.swift(请求/响应格式解码)。若想在本机重新验证,可参考 Tests/integration/conftest.py 的服务器启动约定。

上下文窗口与其他语言

apfel 的上下文窗口按运行环境动态读取:macOS 26 以及 M1/M2 芯片上为 4096 tokens,macOS 27 搭配 M3 或更新芯片且内存 12 GB+(Apple 的 AFM 3 Core Advanced 模型)时为 8192 tokens。运行时可用 apfel –model-info 打印实际值,或直接看 /health 与 /v1/models 返回的 context_window 字段(参考 Sources/Server.swift 与 Sources/Core/ContextWindow.swift 中的 assumedTokens = 4096 兜底逻辑)。

本文所述的 Python 模式与 apfel 的 OpenAI 兼容层完全一致,同一套 HTTP 表面也可以从其他语言复用:Node.js 见 docs/guides/nodejs.md,Ruby 见 docs/guides/ruby.md,PHP 见 docs/guides/php.md,不用 SDK 的纯 HTTP 方案见 docs/guides/bash-curl.md;全部语言指南的索引在 docs/guides/index.md。接口级的能力边界、JSON Schema 支持细节与 Responses API 说明,可进一步阅读 docs/openai-api-compatibility.md。

赞

分享

【免费下载链接】apfel

The free AI already on your Mac. CLI tool, OpenAI-compatible server, and interactive chat — all on-device via Apple Intelligence. No API keys, no cloud, no downloads.

项目地址:
https://gitcode.com/gh_mirrors/apfe/apfel

点击查看 免费下载

上一篇:
Blutter实战指南:深入探索Flutter应用逆向分析

下一篇:
Wireshark 5G网络故障排查实战技巧:从抓包到深度解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

赞(0)
未经允许不得转载:网硕互联帮助中心 » 使用 Python 调用 Apple 端侧 Foundation Model:apfel 本地 OpenAI 兼容服务器实战指南
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!