
最近在 GitHub 上翻 AI 开源项目时频繁看到freellmapi这个关键词。很多开发者把它当成“免费的 LLM API 入口”来搜索也有人误以为它是一个可以直接拿到 Key 的网站。我把相关项目资料、热词讨论和开源仓库的常见组织方式梳理了一遍并结合实际开发经验整理成一篇偏向项目阅读与自建实践的教程。本文会先讲清楚freellmapi这类项目到底是什么为什么会出现再给出一套安全、完整、可落地的自建轻量 LLM API 网关方案包含环境准备、FastAPI 代码、OpenAI 兼容协议解析、运行验证和常见排错。适合正在做 AI 应用 Demo、想统一管理多模型接口、或者准备入门大模型 API 开发的读者。1. freellmapi 是什么1.1 名称拆解freellmapi不是一个官方技术名词它是由三个英文单词组合而来的搜索热词free免费、开源、可白嫖。LLMLarge Language Model大语言模型。APIApplication Programming Interface应用编程接口。把它们组合在一起含义就很直白收录免费大语言模型 API 的项目。在 GitHub 上这类项目通常以仓库形式存在项目作者把网上可访问的、提供免费额度的模型接口或者开源模型的公共访问地址统一收集到一张列表里。除了单纯的汇总部分项目还提供了统一封装代码、代理转发服务、模型路由逻辑让使用者可以通过一个入口调用多个免费模型。1.2 它解决什么问题实际的 AI 应用开发中很多团队会遇到下面这些情况你准备开发一个 AI 聊天机器人但刚开始阶段不想付费开通模型服务。你需要对比多家模型在同一个问题上的回答效果但每个服务商的 API 格式都不一样。你只想做产品原型验证不想为了一两个 Demo 功能专门申请企业认证。你希望所有模型走同一个Base URL切换模型时只需要改model参数。freellmapi这类项目本质上就是围绕“免费”和“统一接入”这两个诉求做文章。它把各家免费模型的接入地址、认证方式、模型 ID、调用示例整理成文档有的项目还会提供一个轻量中转服务让所有请求先打到自己的服务上再由中转服务转发给真实模型接口。1.3 常见应用场景结合社区里开发者分享的使用经验这类项目的主要使用场景包括场景说明个人学习 Demo快速接入一个免费模型跑通聊天问答多模型效果对比统一格式调用多个模型批量对比输出质量内部工具开发给团队内部的小工具提供基础的文本生成能力教学示例在课程中演示 API 调用流程避免学生付费网关原型设计用免费模型先行设计代理层、限流层、日志层1.4 需要注意的边界这里必须说清楚一点freellmapi不是某个固定的商业产品。网络上搜到的“官网”“入口”往往指向 GitHub 仓库或第三方镜像站点项目本身的维护情况、接口稳定性、免费额度随时可能变化。使用前一定要阅读对应项目的 README确认它提供的是“文档汇总”还是“转发服务”并评估安全风险。2. 为什么 freellmapi 这类项目会流行2.1 大模型 API 接入成本仍然存在虽然开源大模型越来越多但普通开发者在本地跑一个可用的大模型仍然需要一定的显卡资源。对大多数做上层应用开发的程序员来说更高效的方式是直接调用线上 API。线上 API 的接入成本包括注册开发者账号部分平台需要企业认证。下载多套 SDK学习不同的鉴权方式。阅读和项目无关的大量接口文档。为流量和 Token 付费。当这些成本叠加在一起开发者自然会去寻找一个更轻量、更标准的入口。freellmapi类项目把“接入体验”简化成了“复制 Key 改 Base URL”这种模式天然具备传播力。2.2 免费额度政策让聚合类项目有了生存空间国内外不少大模型服务商都提供新用户免费体验额度或者在限时活动期间开放免费调用。这些额度通常足够支撑学习和小规模测试。但免费额度有几个特点有时间限制过了活动期就失效。模型 ID 可能不定期调整。接口限制严格并发并发数比较低。不同平台的免费策略差异很大。聚合类项目正好承担了“信息整理”和“策略适配”的角色。有人把各家免费额度的申请页面、模型 ID、限流规则集中维护后来者就不用一个个去翻文档了。2.3 开发者的“统一接入”需求被放大如果你对接过两个以上的模型服务商就会明显感觉到不同平台之间 API 风格差异很大。有的使用 HTTP Header 鉴权有的使用 Query 参数有的需要先获取临时 Token。为了让上层业务代码不被某个具体厂商绑定团队通常会自己封装一层“模型网关”。freellmapi类项目可能是这个需求的雏形先收集免费接口再用统一格式转发。这也是很多开发者愿意关注这类项目的原因——他们不只是想白嫖 API更想参考项目中的网关设计思路。3. 如何正确阅读 freellmapi 类 GitHub 项目如果你在 GitHub 上搜索freellmapi可能会看到多个同名或相似命名的仓库。不要看到一个仓库就直接复制 Key 使用建议按照下面的顺序阅读。3.1 先看 README 的定位说明一个合格的聚合项目README 开头会明确说明自己是“纯文档”还是“可部署服务”。如果 README 里出现以下关键词基本可以判断项目性质README 表述项目性质free API list/awesome collection文档汇总型只提供信息proxy server/gateway/relay转发服务型可以部署simple client/python sdk客户端封装型只负责调用如果是文档汇总型你要做的是按说明去官方渠道申请自己的 Key不要直接把公共 Key 用于生产环境。3.2 检查支持的模型与服务商项目 README 通常会用表格列出支持的服务商、模型名称、基础路径、认证方式等信息。比较完整的表格至少包含服务商名称。模型 ID。免费额度说明。是否需要申请 Key。官方文档地址。要注意这类表格很可能有滞后性。模型 ID 升级、接口停用、免费政策变化都会让表格内容失去准确性。最稳妥的做法是以表格为线索去官方文档二次确认。3.3 查看示例代码与调用格式大部分项目会提供 Python、JavaScript 或 curl 示例。重点关注以下信息Base URL是什么。请求头如何设置。请求体格式是 OpenAI 风格还是服务商自定义风格。响应结果是否能直接解析。下面是一个常见的 OpenAI 兼容格式调用示例适合用来理解聚合项目的接入方式curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer YOUR_API_KEY \ -d { model: your-free-model-id, messages: [ {role: user, content: Hello} ] }如果你发现项目中的示例请求体同时包含prompt、inputs、messages等不同字段说明它内部做了一层协议转换不再是单纯的文档汇总而是一个有代码逻辑的中转服务。3.4 检查许可证与免责条款开源项目不等于可以随意使用。使用freellmapi类项目前重点关注仓库是什么开源许可证MIT、Apache-2.0、GPL 等。是否声明了“不保证接口长期可用”。是否要求你自行申请 Key。是否包含第三方服务的品牌标识。如果项目规则不清晰或者要求你把第三方账号密码提交到它的服务端务必停止使用。4. 环境准备与示例项目结构下面我们进入实操部分。为了让你更清楚freellmapi类项目的内部工作方式我会带你写一个轻量级多模型 API 网关。这个网关不依赖任何付费服务也不收集公共 Key。它的目标很纯粹接收客户端发来的 OpenAI 兼容请求。根据model参数把请求转发到不同后端模型服务商。把响应统一转换为 OpenAI 兼容格式返回。这么做之后你的上层代码只需要维护一套调用方式切换模型时只改model字段即可。4.1 环境说明本文示例使用 Python 实现所需环境如下操作系统Windows / macOS / Linux 均可本文以 macOS Linux 命令为例。Python 版本3.10 或更高。包管理工具pip 或 poetry。HTTP 服务框架FastAPI。HTTP 客户端httpx。接口测试工具curl 或 Postman。注意FastAPI 和 httpx 的版本更新比较快。下面的requirements.txt只给出核心依赖没有写固定版本实际创建虚拟环境后需要安装最新稳定版fastapi uvicorn[standard] httpx pydantic python-dotenv使用下面命令安装依赖mkdir freellm-gateway cd freellm-gateway python3 -m venv venv source venv/bin/activate pip install -r requirements.txt4.2 项目结构为了便于阅读我们把代码拆分成四个文件职责区分清楚freellm-gateway/ ├── requirements.txt ├── .env.example ├── main.py ├── router.py ├── service.py └── config.pyconfig.py读取环境变量。service.py封装调用后端模型的逻辑。router.py定义 HTTP API 路由。main.py创建 FastAPI 应用。5. 核心配置与代码实现5.1 配置管理 config.py网关需要支持多个后端模型服务我们应该把每个服务商的Base URL、API Key、默认模型 ID 放到环境变量中避免写死在代码里。创建.env.example# 服务商 A 的配置 PROVIDER_A_API_KEYyour_key_here PROVIDER_A_BASE_URLhttps://api.example-a.com/v1 PROVIDER_A_MODELfree-chat-model # 服务商 B 的配置 PROVIDER_B_API_KEYyour_key_here PROVIDER_B_BASE_URLhttps://api.example-b.com/v1 PROVIDER_B_MODELfree-chat-model复制为.env并填入真实 Key 后config.py负责加载它们# 文件路径config.py import os from dotenv import load_dotenv load_dotenv() class ProviderConfig: 单个模型服务商的配置信息 def __init__(self, name: str, api_key: str, base_url: str, model: str): self.name name self.api_key api_key self.base_url base_url.rstrip(/) self.model model def load_provider_configs() - dict[str, ProviderConfig]: 从环境变量中加载所有服务商配置 providers {} # 注意实际项目中建议设计成循环读取 PROVIDER_1..N # 这里为了演示只读取两个固定的服务商 if os.getenv(PROVIDER_A_API_KEY): providers[service-a] ProviderConfig( nameservice-a, api_keyos.getenv(PROVIDER_A_API_KEY, ), base_urlos.getenv(PROVIDER_A_BASE_URL, https://api.example-a.com/v1), modelos.getenv(PROVIDER_A_MODEL, free-chat-model), ) if os.getenv(PROVIDER_B_API_KEY): providers[service-b] ProviderConfig( nameservice-b, api_keyos.getenv(PROVIDER_B_API_KEY, ), base_urlos.getenv(PROVIDER_B_BASE_URL, https://api.example-b.com/v1), modelos.getenv(PROVIDER_B_MODEL, free-chat-model), ) return providers这里的关键点在于真实项目中不要只写两个固定的 if 分支。更好的做法是读取PROVIDER_COUNT环境变量通过循环构造配置列表。上面代码保持简单是为了让你聚焦理解数据结构。5.2 对接服务商service.py各服务商的鉴权方式并不完全相同但在“OpenAI 兼容协议”下绝大多数服务商都接受Authorization: Bearer key的请求头。service.py的核心职责有两个把客户端请求转换成目标服务商需要的格式。调用目标服务商接口并把响应转换成统一格式。# 文件路径service.py import httpx from config import ProviderConfig DEFAULT_TIMEOUT 60.0 class LLMServiceError(Exception): 调用上游模型服务失败时抛出 async def chat_completion( provider: ProviderConfig, messages: list[dict], temperature: float 0.7, ) - dict: 调用指定服务商的 chat/completions 接口。 这里假设目标服务商兼容 OpenAI 的 /v1/chat/completions 协议。 不同服务商的路径可能不同可以在 ProviderConfig 中增加 path 字段扩展。 url f{provider.base_url}/chat/completions headers { Authorization: fBearer {provider.api_key}, Content-Type: application/json, } payload { model: provider.model, messages: messages, temperature: temperature, } async with httpx.AsyncClient(timeoutDEFAULT_TIMEOUT) as client: resp await client.post(url, headersheaders, jsonpayload) if resp.status_code ! 200: raise LLMServiceError( fprovider {provider.name} returned status {resp.status_code}: {resp.text} ) return resp.json()上面的代码有几个可以扩展的点如果服务商 A 需要把messages转换成prompt你可以在ProviderConfig中增加request_transform回调。如果服务商 B 使用自定义签名鉴权可以在service.py中为它单独写一个_build_headers函数。如果希望支持流式输出需要把stream参数加入payload并使用httpx.AsyncClient.stream读取 SSE 数据。5.3 定义 HTTP 路由router.py在 FastAPI 中我们把客户端请求接收到/v1/chat/completions并根据model参数选择对应的服务商。# 文件路径router.py from fastapi import APIRouter, HTTPException from pydantic import BaseModel, Field import service from config import load_provider_configs router APIRouter(prefix/v1) class ChatMessage(BaseModel): role: str content: str class ChatCompletionRequest(BaseModel): model: str Field(..., description模型 ID用于选择服务商) messages: list[ChatMessage] temperature: float 0.7 router.post(/chat/completions) async def chat_completions(request: ChatCompletionRequest): providers load_provider_configs() # 简单映射model 字段中包含服务商名称前缀 # 例如 modelservice-a:free-chat-model if : in request.model: provider_name, _ request.model.split(:, 1) else: provider_name request.model provider providers.get(provider_name) if provider is None: raise HTTPException(status_code404, detailfunknown provider: {provider_name}) messages [msg.model_dump() for msg in request.messages] try: result await service.chat_completion( providerprovider, messagesmessages, temperaturerequest.temperature, ) except service.LLMServiceError as exc: raise HTTPException(status_code502, detailstr(exc)) from exc return result路由层的设计思路是model参数格式设计为服务商名:真实模型ID。比如service-a:free-chat-model。先通过前缀找到服务商配置。再把messages透传给service.chat_completion。如果上游服务失败HTTP 状态码返回 502。这里有一点要注意pydantic 的model_dump()方法在 v2 中可用v1 中应该使用.dict()。如果你的环境还是 FastAPI 依赖 pydantic v1需要根据版本调整。5.4 启动入口main.py最后是 FastAPI 应用入口。# 文件路径main.py from fastapi import FastAPI from router import router app FastAPI( titleFree LLM Gateway, description统一接入多个大模型 API 的轻量网关示例, version0.1.0, ) app.include_router(router) app.get(/health) async def health_check(): return {status: ok}启动服务uvicorn main:app --reload --port 8000正常情况下终端会输出INFO: Uvicorn running on http://127.0.0.1:8000 INFO: Application startup complete.6. 运行与验证6.1 健康检查打开新终端执行curl http://127.0.0.1:8000/health预期返回{status:ok}6.2 调用聊天接口假设你在.env中配置了PROVIDER_A_API_KEY并且服务商 A 是一个 OpenAI 兼容接口。执行curl http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: service-a:free-chat-model, messages: [ {role: user, content: 请用一句话介绍大模型 API} ], temperature: 0.7 }请求到达网关后的流转过程如下FastAPI 接收请求并验证ChatCompletionRequest格式。路由从model参数中解析出provider_name。网关读取配置找到服务商 A 的base_url、api_key、model。httpx向服务商 A 发起真实请求。服务商返回 JSON 后网关把响应原样返回给客户端。如果一切正常你会收到和直接调用服务商 A 时几乎一样的 JSON 结构。6.3 验证未知服务商请求一个不存在的服务商curl -i http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: fake-provider:test, messages: [{role: user, content: hello}] }预期状态码是404响应体类似于{detail: unknown provider: fake-provider}到这里你已经搭建了一个最小可运行的多模型网关。freellmapi仓库中许多转发类项目核心逻辑与上面的代码是相似的差别只在于配置的服务商数量更多、协议转换更复杂、增加了数据库中转计费等功能。7. 常见问题与排查思路在自建或使用freellmapi类项目时比较常见的问题集中在依赖版本、请求格式、上游权限三个方面。我把高频问题整理如下。问题现象常见原因解决思路启动报ModuleNotFoundError未安装依赖或虚拟环境未激活检查pip install -r requirements.txt激活虚拟环境请求返回 404model参数中的服务商前缀未匹配确认服务商已在配置中注册检查拼接规则返回 401 UnauthorizedAPI Key 错误、过期或被上游拒绝核对.env中的 Key直接 curl 上游接口确认返回 400 Bad Request请求体字段不兼容服务商要求不同字段打开上游接口文档对照payload字段处理返回 502 Bad Gateway上游服务异常、超时或网络波动查看网关日志确认上游接口地址是否可达返回结果缺少choices字段上游响应格式不是 OpenAI 兼容格式增加协议转换逻辑从上游响应中提取文本中文乱码或 Unicode 错误编码处理不统一在请求和响应中显式使用 UTF-8流式输出无法工作stream参数或 SSE 解析未实现使用 httpx 流式读取按data:行解析事件7.1 排查思路建议遇到问题不要急着改代码建议按下面的顺序排查。首先看网络层。直接使用 curl 调用上游服务商接口确认你的网络环境、Key 有效性以及上游接口本身是否正常。然后看协议层。把客户端发给网关的请求体抓下来对照上游服务商的文档检查model、messages、temperature字段。很多免费接口要求某些参数必须为整数或者限制了max_tokens的默认值。最后看应用层。确认网关日志里打印的最终请求 URL、请求头和请求体是否和预期一致。如果使用 FastAPI可以在service.py中临时增加print日志print(f[DEBUG] url{url}) print(f[DEBUG] headers{headers}) print(f[DEBUG] payload{payload})这样可以快速定位是网关转换问题还是上游服务问题。8. 最佳实践与工程建议8.1 不要把 Key 写进代码无论你使用的是freellmapi中的公共接口还是自己申请的服务商 Key都必须通过环境变量或密钥管理服务注入。建议的配置管理方式本地开发使用.env且把.env加入.gitignore。服务器部署使用 Docker 环境变量或 K8s Secret。团队协作使用 Vault、AWS Secrets Manager 等密钥管理工具。8.2 为每个服务商设置独立超时与重试免费接口往往伴随较高的延迟波动。统一使用 60 秒超时可能导致某些请求长时间挂起。建议在ProviderConfig中增加timeout: float 60.0 max_retries: int 1重试时注意只有幂等请求才适合自动重试。如果请求已经在上游产生计费 Token重试可能造成重复扣费或重复输出。8.3 统一响应结构不同服务商返回的响应结构差异很大有的返回choices有的返回response有的返回outputs。为了让上层业务代码不感知这些差异网关层应该做一次标准化。建议的最小统一响应结构{ id: chatcmpl-xxx, object: chat.completion, created: 1710000000, model: actual-model-id, choices: [ { index: 0, message: { role: assistant, content: 模型生成的文本 }, finish_reason: stop } ], usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 } }如果上游没有返回usage网关可以结合字符数做粗略估算也可以把 usage 设为null。8.4 接入限流与熔断自由使用免费接口时过高的并发可能触发上游封禁。网关层至少要支持全局限流所有请求每秒最大数量。服务商独立限流某个服务商每秒最大数量。熔断开关当某个服务商连续失败超过阈值时直接返回快速失败。实现方式可以使用 FastAPI 依赖注入配合 Redis 计数器。如果只是小型内部项目也可以用内存版令牌桶但要注意进程重启后状态会丢失。8.5 记录结构化日志错误排查过程中日志是最重要的信息来源。建议记录以下信息请求 ID。上游服务商。模型 ID。Token 消耗。响应耗时。状态码。错误摘要。日志中不要记录完整的 API Key 和完整请求内容防止敏感信息泄漏。8.6 遵守服务商使用条款免费额度通常带有明确的使用限制例如只用于学习产品原型禁止商用。单日调用次数上限。禁止批量注册刷接口。禁止通过代理二次分发。使用freellmapi类项目时不要因为接口是免费的就把网关部署到公网大规模提供转发服务。这类行为不仅违反服务商条款也可能给项目作者和接口维护方带来风险。合规使用才能让免费生态持续下去。9. 总结与下一步学习方向通过这篇教程你经历了三个层次的提升。第一理解了freellmapi类项目的本质。它不是单一产品而是一类“免费大模型 API 聚合与转发”的开源解决方案。入口通常是 GitHub 仓库内容可能是文档汇总、客户端封装也可能是可部署的代理服务。第二学会了阅读聚合项目的关键方法。先确认项目性质再检查服务商列表和调用格式然后验证许可证与免责条款最后在测试环境中运行。第三实现了一个最小可运行的 OpenAI 兼容多模型网关。代码中包含配置管理、路由选择、上游调用、异常处理是理解更大规模 AI 网关项目的基线。如果你希望继续深入下面的方向可以按兴趣选择学习 SSE 协议为网关增加流式输出能力。研究令牌桶限流算法保护免费接口不被过量请求打爆。云厂商的免费额度文档扩展自己的服务商配置。尝试把网关部署到 Docker加入监控和告警能力。最后留下一个动手练习把service.py中的请求转换逻辑抽象成自定义函数让服务商 A 使用messages格式服务商 B 使用prompt格式然后分别验证两者能否在同一个路由下正常工作。完成这个练习后你对网关协议转换的理解会比现在更深一层。