Documentation Index
Fetch the complete documentation index at: https://tech.illasoft.com/llms.txt
Use this file to discover all available pages before exploring further.
架构概览
核心组件
| 组件 | 作用 | 说明 |
|---|
| Voyage AI | 图片 Embedding | voyage-multimodal-3.5 模型,512 维向量 |
| LanceDB | 向量数据库 | 存储和搜索相似图片 |
| 内容审核 | 图片+文本审核 | 检测 NSFW/违规内容 |
发布流程
1. 生成 Embedding
使用 Voyage AI 的多模态模型生成图片向量:
export const getImageEmbedding = async (imageId: string) => {
const imageUrl = await getMessageFileUrl(imageId);
const response = await fetch("https://api.voyageai.com/v1/multimodalembeddings", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.VOYAGE_API_KEY}`,
},
body: JSON.stringify({
model: "voyage-multimodal-3.5",
input_type: "document",
inputs: [
{
content: [{ type: "image_url", image_url: imageUrl }],
},
],
output_dimension: 512,
}),
});
const result = await response.json();
return result.data?.[0]?.embedding;
};
2. 相似度检查
在 LanceDB 中检查是否有相似图片(只与当前用户的图片对比):
export async function hasSimilarInLanceDB(
userId: string,
embedding: number[],
threshold: number = 0.25, // cosine distance < 0.25 = 相似度 > 75%
): Promise<boolean> {
const table = await getFeedTable();
const results = await table
.search(embedding)
.where(`user_id = '${userId}'`)
.distanceType("cosine")
.limit(1)
.toArray();
return results.length > 0 && results[0]._distance < threshold;
}
相似度阈值 0.25 表示 cosine distance,对应相似度约 75%。只与同一用户的图片对比,不同用户之间不去重。
3. 内容审核
同时审核图片和文本(prompt):
const [imageModeration, textModeration] = await Promise.all([
moderateImageUrl(newImageId),
moderateText(image.prompt),
]);
const moderationPassed = imageModeration && textModeration;
4. 可见性判断(Flag 系统)
可见性使用 flag 数组决定。flag 为空时 Feed 可见,非空时不可见:
const flag: string[] = [];
if (userBanned) flag.push("ban"); // profile.flag includes "ban"
if (isSimilar) flag.push("duplicate"); // LanceDB vector similarity check
if (!moderationPassed) flag.push("nsfw"); // OpenAI moderation
// Feed is visible when flag array is empty
| Flag 值 | 说明 |
|---|
"ban" | 用户被封禁(profile.flag 包含 “ban”) |
"duplicate" | 与已有图片相似(LanceDB 向量检查) |
"nsfw" | 内容审核不通过(图片或文本) |
5. 存储 Embedding
发布成功后,将 embedding 存入 LanceDB:
if (!error && feedData && embedding) {
await saveFeedEmbedding({
id: feedData.id,
user_id: resourceId,
image_id: image.imageId,
vector: embedding,
created_at: new Date().toISOString(),
});
}
LanceDB 配置
const db = await lancedb.connect(process.env.LANCEDB_URI!, {
apiKey: process.env.LANCEDB_API_KEY,
});
表结构
type FeedEmbedding = {
id: string; // feed ID
user_id: string; // 用户 ID
image_id: string; // 图片 ID
vector: number[]; // 512 维向量
created_at: string;
};
环境变量
LANCEDB_URI=<lancedb-cloud-uri>
LANCEDB_API_KEY=<api-key>
VOYAGE_API_KEY=<voyage-api-key>
Voyage AI 配置
| 参数 | 值 | 说明 |
|---|
| model | voyage-multimodal-3.5 | 多模态 embedding 模型 |
| input_type | document | 输入类型 |
| output_dimension | 512 | 输出向量维度 |
Voyage AI 按 token 计费,每次调用会消耗一定费用。建议在自动发布场景中谨慎使用。
Analytics 事件
发布时记录 PostHog 事件:
posthog.capture({
distinctId: resourceId,
event: "feed_published",
properties: {
feed_id: feedData?.id,
media_type: "image", // 或 "video" / "audio"
image_id: image.imageId,
feed_image_id: newImageId,
flag,
},
});
flag 值
flag 属性为字符串数组,可能包含的值:"ban" | "duplicate" | "nsfw"。空数组表示 Feed 可见。
删除流程
删除 Feed 时同时删除 LanceDB 中的 embedding:
export async function deleteFeedEmbedding(feedId: string): Promise<void> {
const table = await getFeedTable();
await table.delete(`id = '${feedId}'`);
}
完整流程图
三路发布对照
POST /publish/、POST /publish/video、POST /publish/audio 三个端点流程相似但有关键差异。
| 步骤 | Image(POST /publish/) | Video(POST /publish/video) | Audio(POST /publish/audio) |
|---|
| 复制文件 | copyImageFromMessageToFeed() | copyVideoFromMessageToFeed()(视频 + 缩略图) | copyVideoFromMessageToFeed()(音频 + 可选 cover) |
| 去重 key | origin_image_id = image ID | origin_image_id = video ID | origin_image_id = audio ID |
| Embedding 源 | 原图 | 缩略图 (thumbId) | 封面图 (coverId,如有) |
| LanceDB 去重 | ✅ 当前用户范围 | ✅ 使用缩略图向量 | ✅ 使用封面向量(无 cover 时跳过) |
| CSAM 审核 | 图片 + prompt 文本 | 缩略图 + prompt 文本 | 封面 + lyrics 全文 + prompt |
| media_type | null(默认) | "video" | "audio" |
| Flag 来源 | nsfw / duplicate / ban | 同 | 同 |
| PostHog | feed_published | 同 + feed_video_id | 同 + feed_audio_id |
feed 表 media_type 默认值:图片 feed 的 media_type 为 null(不是 "image")。前端查询图片 feed 时需用 media_type.is.null 或 media_type.eq."image"。
Audio 发布的三路 CSAM
const [coverMod, lyricsMod, promptMod] = await Promise.all([
audio.coverId ? moderateImageUrl(newCoverId) : Promise.resolve(true),
moderateText(audio.lyrics ?? ""),
moderateText(audio.prompt ?? ""),
]);
const moderationPassed = coverMod && lyricsMod && promptMod;
相关文档