init
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
# ComfyUI OpenAI Compatible API Node
|
||||
|
||||
一个支持 OpenAI 兼容 API 的 ComfyUI 自定义节点,可以调用任何兼容 OpenAI API 格式的大语言模型服务(如 vLLM、LM Studio、Ollama、LocalAI 等)。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 支持纯文本对话
|
||||
- ✅ 支持图片+文本的视觉对话(Vision API)
|
||||
- ✅ 完全兼容 OpenAI Chat Completions API 格式
|
||||
- ✅ 支持自定义 API 端点
|
||||
- ✅ 支持温度、最大 token 数等参数调节
|
||||
- ✅ 支持图片细节控制(auto/low/high)
|
||||
|
||||
## 安装方法
|
||||
|
||||
### 方法一:直接克隆到 custom_nodes 目录
|
||||
|
||||
```bash
|
||||
cd ComfyUI/custom_nodes
|
||||
git clone https://github.com/your-repo/ComfyUI-OpenAI-Compatible-API.git
|
||||
```
|
||||
|
||||
### 方法二:手动下载
|
||||
|
||||
1. 下载本仓库的所有文件
|
||||
2. 将文件夹复制到 `ComfyUI/custom_nodes/` 目录下
|
||||
3. 重启 ComfyUI
|
||||
|
||||
### 安装依赖
|
||||
|
||||
本节点需要以下 Python 包(ComfyUI 通常已包含):
|
||||
|
||||
```bash
|
||||
pip install requests pillow torch numpy
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 基本参数
|
||||
|
||||
- **prompt**: 发送给模型的文本提示词
|
||||
- **endpoint**: API 端点地址(例如:`http://localhost:3010/v1/chat/completions`)
|
||||
- **model**: 模型名称或路径
|
||||
- **max_tokens**: 最大生成 token 数(1-32000)
|
||||
- **temperature**: 温度参数,控制生成的随机性(0.0-2.0)
|
||||
|
||||
### 可选参数
|
||||
|
||||
- **image**: 图片输入(支持 ComfyUI 的 IMAGE 类型)
|
||||
- **api_key**: API 密钥(如果服务需要)
|
||||
- **image_detail**: 图片细节级别
|
||||
- `auto`: 自动选择
|
||||
- `low`: 低细节(更快,更便宜)
|
||||
- `high`: 高细节(更详细,更慢)
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例 1:纯文本对话
|
||||
|
||||
1. 添加 `OpenAI Compatible LLM` 节点
|
||||
2. 设置 `endpoint` 为你的 API 地址
|
||||
3. 设置 `model` 为模型名称
|
||||
4. 在 `prompt` 中输入你的问题
|
||||
5. 连接到输出节点查看结果
|
||||
|
||||
### 示例 2:图片+文本对话(视觉模型)
|
||||
|
||||
1. 添加 `Load Image` 节点加载图片
|
||||
2. 添加 `OpenAI Compatible LLM` 节点
|
||||
3. 将图片输出连接到节点的 `image` 输入
|
||||
4. 在 `prompt` 中输入关于图片的问题(如 "描述这张图片")
|
||||
5. 确保 `model` 是支持视觉的模型(如 Qwen-VL、LLaVA 等)
|
||||
6. 连接到输出节点查看结果
|
||||
|
||||
## 兼容的后端服务
|
||||
|
||||
本节点兼容任何实现了 OpenAI Chat Completions API 的服务:
|
||||
|
||||
- **vLLM**: 高性能推理服务器
|
||||
- **Ollama**: 本地大模型运行工具
|
||||
- **LM Studio**: 桌面端大模型应用
|
||||
- **LocalAI**: 本地 AI API 服务
|
||||
- **Text Generation WebUI**: OpenAI API 扩展
|
||||
- **OpenAI 官方 API**: 需要设置正确的 endpoint 和 api_key
|
||||
|
||||
## 支持的视觉模型
|
||||
|
||||
如果要使用图片输入功能,你的模型需要支持视觉输入,例如:
|
||||
|
||||
- Qwen-VL / Qwen2-VL
|
||||
- LLaVA
|
||||
- MiniGPT-4
|
||||
- BLIP-2
|
||||
- CogVLM
|
||||
- GPT-4 Vision(通过 OpenAI API)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保你的 API 服务正在运行并且可访问
|
||||
2. 如果使用图片输入,确保模型支持视觉功能
|
||||
3. 某些服务可能需要 API 密钥
|
||||
4. 超时时间设置为 120 秒,处理大图片或长文本时可能需要调整
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 连接错误
|
||||
|
||||
- 检查 endpoint 地址是否正确
|
||||
- 确认服务是否正在运行
|
||||
- 检查防火墙设置
|
||||
|
||||
### 模型不支持图片
|
||||
|
||||
- 确认模型是否为视觉模型
|
||||
- 检查 API 服务的日志
|
||||
- 尝试纯文本对话测试连接
|
||||
|
||||
### API 密钥错误
|
||||
|
||||
- 确认 api_key 是否正确
|
||||
- 检查服务是否需要认证
|
||||
|
||||
## 开发和贡献
|
||||
|
||||
欢迎提交 Issue 和 Pull Request!
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
||||
## 致谢
|
||||
|
||||
感谢 ComfyUI 社区和所有贡献者!
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
ComfyUI OpenAI Compatible API Node
|
||||
支持调用 OpenAI 兼容的 API,包括文本和图片输入
|
||||
"""
|
||||
|
||||
from .openai_llm_node import OpenAICompatibleLLM
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"OpenAICompatibleLLM": OpenAICompatibleLLM
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"OpenAICompatibleLLM": "OpenAI Compatible LLM"
|
||||
}
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
OpenAI Compatible API Node for ComfyUI
|
||||
支持文本和图片输入,调用兼容 OpenAI 的 API
|
||||
"""
|
||||
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import requests
|
||||
from PIL import Image
|
||||
import torch
|
||||
import numpy as np
|
||||
|
||||
|
||||
class OpenAICompatibleLLM:
|
||||
"""
|
||||
OpenAI 兼容 API 调用节点
|
||||
支持纯文本对话和带图片的视觉对话
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": "hello"
|
||||
}),
|
||||
"endpoint": ("STRING", {
|
||||
"default": "http://localhost:3010/v1/chat/completions"
|
||||
}),
|
||||
"model": ("STRING", {
|
||||
"default": ""
|
||||
}),
|
||||
"max_tokens": ("INT", {
|
||||
"default": 2000,
|
||||
"min": 1,
|
||||
"max": 32000,
|
||||
"step": 1
|
||||
}),
|
||||
"temperature": ("FLOAT", {
|
||||
"default": 0.7,
|
||||
"min": 0.0,
|
||||
"max": 2.0,
|
||||
"step": 0.01
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"image": ("IMAGE",),
|
||||
"api_key": ("STRING", {
|
||||
"default": ""
|
||||
}),
|
||||
"image_detail": (["auto", "low", "high"], {
|
||||
"default": "auto"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("response",)
|
||||
FUNCTION = "generate"
|
||||
CATEGORY = "OpenAI Compatible"
|
||||
|
||||
def tensor_to_base64(self, tensor_image):
|
||||
"""
|
||||
将 ComfyUI 的图片 tensor 转换为 base64 编码的字符串
|
||||
ComfyUI 图片格式: [batch, height, width, channels] 范围 0-1
|
||||
"""
|
||||
# 取第一张图片
|
||||
if len(tensor_image.shape) == 4:
|
||||
tensor_image = tensor_image[0]
|
||||
|
||||
# 转换为 numpy array 并调整到 0-255 范围
|
||||
img_np = (tensor_image.cpu().numpy() * 255).astype(np.uint8)
|
||||
|
||||
# 转换为 PIL Image
|
||||
img_pil = Image.fromarray(img_np)
|
||||
|
||||
# 转换为 base64
|
||||
buffered = io.BytesIO()
|
||||
img_pil.save(buffered, format="PNG")
|
||||
img_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
|
||||
|
||||
return f"data:image/png;base64,{img_base64}"
|
||||
|
||||
def generate(self, prompt, endpoint, model, max_tokens, temperature,
|
||||
image=None, api_key=None, image_detail="auto"):
|
||||
"""
|
||||
调用 OpenAI 兼容的 API 生成响应
|
||||
"""
|
||||
|
||||
# 构建消息内容
|
||||
if image is not None:
|
||||
# 如果有图片输入,使用视觉模型的格式
|
||||
image_base64 = self.tensor_to_base64(image)
|
||||
|
||||
message_content = [
|
||||
{
|
||||
"type": "text",
|
||||
"text": prompt
|
||||
},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": image_base64,
|
||||
"detail": image_detail
|
||||
}
|
||||
}
|
||||
]
|
||||
else:
|
||||
# 纯文本对话
|
||||
message_content = prompt
|
||||
|
||||
# 构建请求体
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": message_content
|
||||
}
|
||||
],
|
||||
"max_tokens": max_tokens,
|
||||
"temperature": temperature
|
||||
}
|
||||
|
||||
# 设置请求头
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
if api_key:
|
||||
headers["Authorization"] = f"Bearer {api_key}"
|
||||
|
||||
try:
|
||||
# 发送请求
|
||||
response = requests.post(endpoint, headers=headers, json=payload, timeout=120)
|
||||
response.raise_for_status()
|
||||
|
||||
# 解析响应
|
||||
result = response.json()
|
||||
|
||||
# 提取生成的文本
|
||||
if "choices" in result and len(result["choices"]) > 0:
|
||||
generated_text = result["choices"][0]["message"]["content"]
|
||||
return (generated_text,)
|
||||
else:
|
||||
return ("Error: No response from API",)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
error_msg = f"API Request Error: {str(e)}"
|
||||
if hasattr(e, 'response') and e.response is not None:
|
||||
try:
|
||||
error_detail = e.response.json()
|
||||
error_msg += f"\nDetails: {json.dumps(error_detail, indent=2)}"
|
||||
except:
|
||||
error_msg += f"\nResponse: {e.response.text}"
|
||||
print(error_msg)
|
||||
return (error_msg,)
|
||||
except Exception as e:
|
||||
error_msg = f"Error: {str(e)}"
|
||||
print(error_msg)
|
||||
return (error_msg,)
|
||||
@@ -0,0 +1,4 @@
|
||||
requests
|
||||
pillow
|
||||
torch
|
||||
numpy
|
||||
Reference in New Issue
Block a user