针对LLM的单一函数Jev样式包装器,包括视觉模型
我对Jev和围绕它出现的自托管项目如OpenJev和SemIf感到很有兴趣。阅读它们让我了解了一个不错的技巧:读取LLM的令牌概率。显然对某些人来说这是一个老技巧。见例如OpenAI的logprobs食谱。但对我来说这是新的。我相信基本的想法是写一个这样的提示:状态:我的订单到达时损坏,我想要退款。问题:哪个团队应该处理此事?[A] 账单 [B] 发货 [C] 退货 只回答最佳选项的字母。然后在兼容的对话完成请求中添加几个JSON请求参数:{ "max_completion_tokens": 1, "logprobs": true, "top_logprobs": 20 } LLM API将返回字母以及模型对替代令牌的日志概率。对每个问题重复此过程。强制它只生成一个令牌可以避免冗长的答案,并且非常快速,尽管处理输入仍然需要时间。不过,对于每个问题,如果后端支持,可以使用共享状态前缀进行KV缓存。有趣的是:这也适用于视觉模型。Jev的文档请求格式目前仅描述文本/JSON状态。我为我的本地实验添加了一个用于图像的附件字段。我的示例捕捉了网络摄像头帧,发送base64 JPEG,并打印一个表:是否可见人员,我们是在室内还是室外,场景亮度如何?在我的RTX 3090上使用Gemma 4 12B时,我大约每秒获得1帧,每帧三个问题。我还运行了OpenAI的gpt-6-luna,获得了大约0.2 FPS。显然因为我没有做任何努力来避免通过他们的系统每个问题每帧的单独连接费用。专业的计算机视觉模型当然更有效,但我喜欢这里的灵活性:通过使用简单的文本描述来更改条件。以下是独立的Python示例(OpenCV仅用于方便访问网络摄像头,而不是用于任何实际的计算机视觉):#!/usr/bin/env -S uv run --script # /// script # dependencies = ["opencv-python"] # /// """预览和评分网络摄像头帧与llama.cpp或OpenAI。 uv run webcam.py uv run webcam.py https://api.openai.com/v1 gpt-6-luna OpenAI读取OPENAI_API_KEY。 """ import argparse import base64 import concurrent.futures import datetime import json import math import mimetypes import os import pathlib import time import urllib.parse import urllib.request import cv2 # attachments是我们对Jev请求格式的自定义添加。 data = json.loads(""" { "state": "检查此网络摄像头帧。只对可见的内容进行判断。", "attachments": [], "questions": { "person": { "type": "noul", "instructions": "有没有人可见?" }, "plant": { "type": "noul", "instructions": "有没有植物可见?" }, "setting": { "type": "choice", "instructions": "摄像头在哪里?", "criteria": { "indoors": null, "outdoors": null, "unclear": null } }, "light": { "type": "score", "instructions": "场景亮度如何?", "criteria": [ "黑暗", "昏暗", "明亮" ] } } } """) def score(data, url, model): state = data["state"] if not isinstance(state, str): state = json.dumps(state) # 附件是我们对Jev样式请求格式的扩展: # 图像文件路径或base64数据URL。加载一次以供所有问题使用。 images = [] for attachment in data.get("attachments", []): if attachment.startswith("data:image/"): images.append(attachment) continue path = pathlib.Path(attachment).expanduser() mime_type, _ = mimetypes.guess_type(path) if mime_type not in {"image/png", "image/jpeg", "image/webp", "image/gif"}: raise ValueError(f"不支持的图像文件:{path}") encoded = base64.b64encode(path.read_bytes()).decode() images.append(f"data:{mime_type};base64,{encoded}") # 仅将API密钥发送到OpenAI。 is_openai = urllib.parse.urlsplit(url).hostname == "api.openai.com" headers = {"Content-Type": "application/json"} if is_openai: headers["Authorization"] = "Bearer " + os.environ["OPENAI_API_KEY"] answers = {} for name, question in data["questions"].items(): # 将选择、布尔值和顺序级别表示为字母选项。 if question["type"] == "choice": options = question["criteria"] elif question["type"] == "noul": options = {"true": None, "false": None} | question.get("criteria", {}) elif question["type"] == "score": options = {str(i): description for i, description in enumerate(question["criteria"])} else: raise ValueError(f"未知问题类型:{question['type']}") if not 2 <= len(options) <= 20: raise ValueError("每个问题提供2到20个标准。") letters = "ABCDEFGHIJKLMNOPQRST"[:len(options)] # 询问单个选项字母,因此其日志概率代表该选项。 instructions = question["instructions"] if not isinstance(instructions, str): instructions = json.dumps(instructions) lines = [f"状态: {state} 问题:{instructions} 选项:"] for letter, (key, description) in zip(letters, options.items()): line = f"[{letter}] {key}" if description is not None: line += f": {description}" lines.appen
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡