65 lines
3.1 KiB
C
65 lines
3.1 KiB
C
/* capability.h - 模型能力探测(vision / tools)
|
||
*
|
||
* 为什么需要它:OpenAI 兼容层**没有任何查询能力的接口**。DeepSeek 和 MiMo 的
|
||
* GET /v1/models 都只返回 id / object / owned_by,没有 capability / modality
|
||
* 字段。所以"这个模型收不收图片"只能靠**发一个试探请求**问出来。
|
||
*
|
||
* 这里叫"序列"是因为它不是一次请求,而是有先后的几步:
|
||
*
|
||
* 1. 基线:一句纯文本 + max_tokens=1
|
||
* └ 失败 → 什么都别下结论(可能是 key、网络、模型名的问题)
|
||
* └ 成功 → 说明模型是活的,后面的否定结论才有意义
|
||
* 2. 合并试探:图片 + tools 一起发
|
||
* └ 200 → 两样都支持(常见情况,到此为止,只花了 2 个请求)
|
||
* └ 非 200 → 拆开单独试,才知道是哪一个不行
|
||
* 3. 单独试探:图片、tools 各发一次
|
||
*
|
||
* 关键在于**否定结论只在基线成功时才敢下**:单个试探 400 而基线 200,说明
|
||
* 差别就在那一样东西上。这样不需要去猜服务端的错误文案("不支持图片"、
|
||
* "invalid content"、"multimodal not supported"…… 各家写法都不一样),
|
||
* 也就不会因为文案变化而误判。
|
||
*
|
||
* 猜不准的时候一律按"支持"处理:多暴露一个工具,代价是模型可能白调一次
|
||
* (我们还有 4xx 降级兜底);而错误地藏掉工具,用户会以为模型看不见图。
|
||
*
|
||
* 结果按 base_url + model 缓存,所以**换模型会自动重新测**,切回来则命中缓存。
|
||
*/
|
||
#ifndef LIGENT_CAPABILITY_H
|
||
#define LIGENT_CAPABILITY_H
|
||
|
||
#include "llm.h"
|
||
|
||
#include <stddef.h>
|
||
|
||
#define CAP_UNKNOWN (-1)
|
||
#define CAP_NO 0
|
||
#define CAP_YES 1
|
||
|
||
typedef struct {
|
||
int vision; /* 能不能收图片输入 */
|
||
int tools; /* 能不能收 tools 参数 */
|
||
int probed; /* 真的测过吗(0 = 没测或被禁用,结论一律按"支持") */
|
||
int conclusive; /* 基线成功了吗 —— 只有它成功,否定结论才作数 */
|
||
} Capabilities;
|
||
|
||
/* 探测(或命中缓存)。enable=0 时直接给"没测过"的全支持结果。
|
||
* verbose 会打印每个试探请求的细节。errbuf 收探测过程中的失败原因,
|
||
* 仅供提示,不影响结论。
|
||
* 返回 1 = 这次真的发了请求(调用方可以据此提示用户一次);
|
||
* 返回 0 = 命中缓存或被禁用(什么都没发生,别重复提示)。 */
|
||
int cap_get(const LlmConfig *cfg, int enable,
|
||
Capabilities *out, int verbose, char *errbuf, size_t errlen);
|
||
|
||
/* 只查缓存,不发任何请求。命中返回 1 并回填,没测过返回 0。
|
||
* 给 /tools 这种"想知道但绝不该为此发请求"的地方用。 */
|
||
int cap_cached(const LlmConfig *cfg, Capabilities *out);
|
||
|
||
/* 把已缓存的结果清掉(下次调用重新探测)。
|
||
* 正常换模型不需要它 —— 缓存按 base_url+model 索引;这个给测试用。 */
|
||
void cap_cache_clear(void);
|
||
|
||
/* 给人和日志看的中文说明,如"支持"/"不支持"/"未测"。字面量,不用释放 */
|
||
const char *cap_name(int v);
|
||
|
||
#endif /* LIGENT_CAPABILITY_H */
|