知识库管理
一、接口设计
1. 获取知识库列表
请求方式:GET
接口路径:/api/dataset/{pageNo}/{pageSize}
示例请求:
GET /api/dataset/1/20
示例响应:
{
"message": null,
"data": {
"size": 20,
"total": 1,
"records": [
{
"tenant_id": "0",
"creator": "",
"create_time": 1730769172064,
"embedding_mode_id": "443263808507674624",
"remark": null,
"type": "0",
"updater": "",
"update_time": 1730769172064,
"deleted": 0,
"user_id": "1",
"meta": null,
"name": "ICS 141",
"id": "443309276048408576",
"desc": "ICS 141 课程资料"
}
],
"current": 1
},
"code": 200
}
2. 创建知识库
请求方式:POST
接口路径:/api/dataset
请求体: 注意是 embedding_mode_id
{
"name": "ICS 11",
"desc": "ICS 111 课程资料",
"embedding_mode_id": "443263808507674624",
"type": "0"
}
示例响应:
{
"message": null,
"data": {
"id": "443309276048408576",
"name": "ICS 141",
"desc": "ICS 141 课程资料"
},
"code": 200
}
3. 验证知识库创建额度
请求方式:GET
接口路径:/api/valid/dataset/{size}
示例请求:
http://localhost:10060/api/valid/dataset/50
示例响应:
{
"code": 200,
"message": "成功",
"data": true
}
4. 获取单个知识库详情
请求方式:GET
接口路径:/api/dataset/{id}
示例请求:
http://localhost:10060/api/dataset/443309276048408576
示例响应:
{
"message": null,
"data": {
"tenant_id": "0",
"creator": "",
"create_time": 1730769172064,
"embedding_mode_id": "443263808507674624",
"remark": null,
"type": "0",
"updater": "",
"update_time": 1730769172064,
"deleted": 0,
"user_id": "1",
"meta": null,
"name": "ICS 141",
"id": "443309276048408576",
"desc": "ICS 141 课程资料",
"application_id_list": [],
"document_count": "2",
"char_length": "16300"
},
"code": 200
}
meta 是 jsonb 列,详情接口返回的是 JSON 对象而不是原始列值:通用型知识库为 null,Web 站点知识库里是根地址与选择器。application_id_list、document_count、char_length 供知识库列表与设置页展示使用,id 与计数类字段都以字符串返回,避免前端大整数精度问题。
二、代码实现
1. 控制器层
控制器层负责处理 HTTP 请求,并调用相应的服务层方法。以下是 ApiDatasetController 的实现:
package nexus.io.mosskb.controller;
import java.util.List;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import nexus.io.annotation.Delete;
import nexus.io.annotation.Get;
import nexus.io.annotation.Post;
import nexus.io.annotation.Put;
import nexus.io.annotation.RequestPath;
import nexus.io.jfinal.aop.Aop;
import nexus.io.mosskb.service.kb.MossKbDatasetHitTestService;
import nexus.io.mosskb.service.kb.MossKbDatasetService;
import nexus.io.mosskb.service.kb.MossKbDocumentService;
import nexus.io.mosskb.service.kb.MossKbParagraphServcie;
import nexus.io.mosskb.service.kb.MossKbParagraphSplitService;
import nexus.io.mosskb.service.kb.MossKbProblemService;
import nexus.io.mosskb.service.kb.MossKbWebDatasetService;
import nexus.io.mosskb.vo.KbDatasetModel;
import nexus.io.mosskb.vo.MossKbUpdateDocumentRequestVo;
import nexus.io.mosskb.vo.Paragraph;
import nexus.io.mosskb.vo.ParagraphBatchVo;
import nexus.io.mosskb.vo.ProbrolemCreateBatch;
import nexus.io.model.result.ResultVo;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.utils.json.JsonUtils;
@RequestPath("/api/dataset")
public class ApiDatasetController {
@Get("")
public ResultVo list() {
Long userId = TioRequestContext.getUserIdLong();
return Aop.get(MossKbDatasetService.class).list(userId);
}
@Get("/{pageNo}/{pageSize}")
public ResultVo page(Integer pageNo, Integer pageSize, String name) {
Long userId = TioRequestContext.getUserIdLong();
return Aop.get(MossKbDatasetService.class).page(userId, pageNo, pageSize, name);
}
@Post("")
public ResultVo save(HttpRequest request) {
String bodyString = request.getBodyString();
KbDatasetModel kbDatasetModel = JsonUtils.parse(bodyString, KbDatasetModel.class);
Long userId = TioRequestContext.getUserIdLong();
return Aop.get(MossKbDatasetService.class).save(userId, kbDatasetModel);
}
/** 创建 Web 站点知识库:保存根地址与选择器后由后台线程抓取页面。 */
@Post("/web")
public ResultVo saveWebDataset(HttpRequest request) {
JSONObject input = JSON.parseObject(request.getBodyString());
return Aop.get(MossKbWebDatasetService.class).save(TioRequestContext.getUserIdLong(), input);
}
/** 同步 Web 站点知识库,sync_type 取 replace 或 complete。 */
@Put("/{id}/sync_web")
public ResultVo syncWebDataset(Long id, HttpRequest request) {
return Aop.get(MossKbWebDatasetService.class).sync(TioRequestContext.getUserIdLong(), id,
request.getParam("sync_type"));
}
@Get("/{id}")
public ResultVo get(Long id) {
Long userId = TioRequestContext.getUserIdLong();
return Aop.get(MossKbDatasetService.class).get(userId, id);
}
@Put("/{id}")
public ResultVo updateDataset(Long id, HttpRequest request) {
KbDatasetModel model = JsonUtils.parse(request.getBodyString(), KbDatasetModel.class);
model.setId(id);
return Aop.get(MossKbDatasetService.class).save(TioRequestContext.getUserIdLong(), model);
}
@Put("/{datasetId}/document/{documentId}/paragraph/{paragraphId}")
public ResultVo updateParagraph(Long datasetId, Long documentId, Long paragraphId, HttpRequest request) {
return Aop.get(MossKbParagraphServcie.class).update(TioRequestContext.getUserIdLong(), datasetId, documentId, paragraphId,
JsonUtils.parse(request.getBodyString(), Paragraph.class));
}
@Delete("/{id}")
public ResultVo delete(Long id) {
Long userId = TioRequestContext.getUserIdLong();
return Aop.get(MossKbDatasetService.class).delete(userId, id);
}
@Get("/{id}/hit_test")
public ResultVo hitTest(Long id, HttpRequest request) {
String query_text = request.getParam("query_text");
Double similarity = request.getDouble("similarity");
Integer top_number = request.getInt("top_number");
String search_mode = request.getParam("search_mode");
Long userId = TioRequestContext.getUserIdLong();
return Aop.get(MossKbDatasetHitTestService.class).hitTest(userId, id, query_text, similarity, top_number,
search_mode);
}
@Post("/{id}/document/_bach")
public ResultVo batch(Long id, HttpRequest request) {
String bodyString = request.getBodyString();
Long userId = TioRequestContext.getUserIdLong();
List<ParagraphBatchVo> list = JsonUtils.parseArray(bodyString, ParagraphBatchVo.class);
return Aop.get(MossKbParagraphSplitService.class).batch(userId, id, list);
}
@Get("/{id}/document/{pageNo}/{pageSize}")
public ResultVo pageDocument(Long id, Integer pageNo, Integer pageSize) {
Long userId = TioRequestContext.getUserIdLong();
return Aop.get(MossKbDocumentService.class).page(userId, id, pageNo, pageSize);
}
@Get("/{datasetId}/document")
public ResultVo documentList(Long datasetId) {
Long userId = TioRequestContext.getUserIdLong();
return Aop.get(MossKbDocumentService.class).list(userId, datasetId);
}
@Get("/{datasetId}/document/{documentId}")
public ResultVo getDocument(Long datasetId, Long documentId) {
Long userId = TioRequestContext.getUserIdLong();
return Aop.get(MossKbDocumentService.class).get(userId, datasetId, documentId);
}
@Delete("/{datasetId}/document/{documentId}")
public ResultVo deleteDocument(Long datasetId, Long documentId) {
Long userId = TioRequestContext.getUserIdLong();
return Aop.get(MossKbDocumentService.class).delete(userId, datasetId, documentId);
}
@Put("/{datasetId}/document/{documentId}")
public ResultVo updateDocument(Long datasetId, Long documentId, HttpRequest httpRequest) {
Long userId = TioRequestContext.getUserIdLong();
String bodyString = httpRequest.getBodyString();
if (bodyString == null) {
return ResultVo.fail("request body can not be empty");
}
MossKbUpdateDocumentRequestVo vo = JsonUtils.parse(bodyString, MossKbUpdateDocumentRequestVo.class);
return Aop.get(MossKbDocumentService.class).update(userId, datasetId, documentId, vo);
}
/** 按地址列表向 Web 站点知识库导入网页文档。 */
@Post("/{datasetId}/document/web")
public ResultVo importWebDocument(Long datasetId, HttpRequest request) {
JSONObject input = JSON.parseObject(request.getBodyString());
return Aop.get(MossKbWebDatasetService.class).importDocuments(TioRequestContext.getUserIdLong(), datasetId, input);
}
/** 重新抓取一个网页文档。 */
@Put("/{datasetId}/document/{documentId}/sync")
public ResultVo syncWebDocument(Long datasetId, Long documentId) {
return Aop.get(MossKbWebDatasetService.class).syncDocument(TioRequestContext.getUserIdLong(), datasetId, documentId);
}
/** 批量同步网页文档,请求体为 {"id_list":[...]}。 */
@Put("/{datasetId}/document/_bach")
public ResultVo batchSyncWebDocument(Long datasetId, HttpRequest request) {
JSONObject input = JSON.parseObject(request.getBodyString());
return Aop.get(MossKbWebDatasetService.class).batchSyncDocuments(TioRequestContext.getUserIdLong(), datasetId,
input == null ? null : input.getJSONArray("id_list"));
}
@Get("/{datasetId}/application")
public ResultVo getApplicationByDatasetId(Long datasetId) {
return Aop.get(MossKbDatasetService.class).getApplicationByDatasetId(datasetId);
}
@Post("/{datasetId}/document/{documentId}/paragraph")
public ResultVo createParagraph(Long datasetId, Long documentId, HttpRequest request) {
String bodyString = request.getBodyString();
Paragraph paragraph = JsonUtils.parse(bodyString, Paragraph.class);
Long userId = TioRequestContext.getUserIdLong();
return Aop.get(MossKbParagraphServcie.class).create(userId, datasetId, documentId, paragraph);
}
// paragraph
@Get("/{datasetId}/document/{documentId}/paragraph/{pageNo}/{pageSize}")
public ResultVo listParagraph(Long datasetId, Long documentId, Integer pageNo, Integer pageSize) {
Long userId = TioRequestContext.getUserIdLong();
return Aop.get(MossKbParagraphServcie.class).page(userId, datasetId, documentId, pageNo, pageSize);
}
@Get("/{datasetId}/document/{documentId}/paragraph/{paragraphId}/problem")
public ResultVo listProblem(Long datasetId, Long documentId, Long paragraphId) {
return Aop.get(MossKbParagraphServcie.class).listProblemByParagraphId(datasetId, documentId, paragraphId);
}
@Post("/{datasetId}/document/{documentId}/paragraph/{paragraphId}/problem")
public ResultVo addProblems(Long datasetId, Long documentId, Long paragraphId, HttpRequest request) {
String bodyString = request.getBodyString();
String content = JsonUtils.parseToMap(bodyString, String.class, String.class).get("content");
return Aop.get(MossKbParagraphServcie.class).addProblemById(datasetId, documentId, paragraphId, content);
}
@Post("/{datasetId}/problem")
public ResultVo createProblem(Long datasetId, HttpRequest request) {
String bodyString = request.getBodyString();
List<String> problems = JsonUtils.parseArray(bodyString, String.class);
return Aop.get(MossKbProblemService.class).create(datasetId, problems);
}
@Get("/{datasetId}/problem/{pageNo}/{pageSize}")
public ResultVo pageDatasetProbleam(Long datasetId, Integer pageNo, Integer pageSize) {
return Aop.get(MossKbProblemService.class).page(datasetId, pageNo, pageSize);
}
// api/dataset/443309276048408576/problem/444734959665311744/paragraph
@Get("/{datasetId}/problem/{problemId}/paragraph")
public ResultVo listParagraphByProblemId(Long datasetId, Long problemId) {
return Aop.get(MossKbProblemService.class).listParagraphByProblemId(datasetId, problemId);
}
@Put("/{datasetId}/document/{documentId}/paragraph/{paragraphId}/problem/{problemId}/association")
public ResultVo association(Long datasetId, Long documentId, Long paragraphId, Long problemId) {
return Aop.get(MossKbProblemService.class).association(datasetId, documentId, paragraphId, problemId);
}
@Put("/{datasetId}/document/{documentId}/paragraph/{paragraphId}/problem/{problemId}/un_association")
public ResultVo unAssociation(Long datasetId, Long documentId, Long paragraphId, Long problemId) {
return Aop.get(MossKbProblemService.class).unAssociation(datasetId, documentId, paragraphId, problemId);
}
@Post("/{datasetId}/problem/_batch")
public ResultVo addProbrolems(Long datasetId, HttpRequest request) {
String bodyString = request.getBodyString();
ProbrolemCreateBatch batchReequest = JsonUtils.parse(bodyString, ProbrolemCreateBatch.class);
return Aop.get(MossKbProblemService.class).addProbrolems(datasetId, batchReequest);
}
@Delete("/{datasetId}/problem/{problemId}")
public ResultVo deleteProblem(Long datasetId, Long problemId) {
return Aop.get(MossKbProblemService.class).delete(datasetId, problemId);
}
@Delete("/{datasetId}/problem/_batch")
public ResultVo deleteProblemBatch(Long datasetId, HttpRequest request) {
String bodyString = request.getBodyString();
List<Long> problems = JsonUtils.parseArray(bodyString, Long.class);
return Aop.get(MossKbProblemService.class).delete(datasetId, problems);
}
}
2. 服务层
服务层负责业务逻辑的处理。以下是 MossKbDatasetService 的实现:
package nexus.io.mosskb.service.kb;
import java.util.ArrayList;
import java.util.List;
import com.jfinal.kit.Kv;
import nexus.io.db.TableInput;
import nexus.io.db.TableResult;
import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.jfinal.aop.Aop;
import nexus.io.kit.RowUtils;
import nexus.io.mosskb.constant.MossKbTableNames;
import nexus.io.mosskb.dao.MossKbDatasetDao;
import nexus.io.mosskb.model.MossKbDataset;
import nexus.io.mosskb.vo.KbDatasetModel;
import nexus.io.mosskb.vo.ResultPage;
import nexus.io.model.page.Page;
import nexus.io.model.result.ResultVo;
import nexus.io.table.constants.Operators;
import nexus.io.table.services.ApiTable;
public class MossKbDatasetService {
private String application_mapping_count_sql = String.format("select count(1) from %s where dataset_id=?", MossKbTableNames.moss_kb_application_dataset_mapping);
private String document_count_sql = String.format("select count(1) from %s where dataset_id=?", MossKbTableNames.moss_kb_document);
private String sum_char_length_sql = String.format("select sum(char_length) from %s where dataset_id=?", MossKbTableNames.moss_kb_document);
public ResultVo page(Long userId, Integer pageNo, Integer pageSize, String name) {
TableInput tableInput = new TableInput();
tableInput.setPageNo(pageNo).setPageSize(pageSize);
if (userId.equals(1L)) {
} else {
tableInput.set("user_id", userId);
}
if (name != null) {
tableInput.set("name", name).set("name_op", Operators.CT);
}
TableResult<Page<Row>> tableResult = ApiTable.page(MossKbTableNames.moss_kb_dataset, tableInput);
Page<Row> page = tableResult.getData();
int totalRow = page.getTotalRow();
List<Row> list = page.getList();
List<Kv> kvs = new ArrayList<>();
for (Row record : list) {
Kv kv = record.toKv();
Long datasetId = kv.getLong("id");
Long application_mapping_count = Db.queryLong(application_mapping_count_sql, datasetId);
kv.set("application_mapping_count", application_mapping_count);
Long document_count = Db.queryLong(document_count_sql, datasetId);
kv.set("document_count", document_count);
Long charLength = Db.queryLong(sum_char_length_sql, datasetId);
kv.set("char_length", charLength);
kvs.add(kv);
}
ResultPage<Kv> resultPage = new ResultPage<>(pageNo, pageSize, totalRow, kvs);
return ResultVo.ok(resultPage);
}
public ResultVo save(Long userId, KbDatasetModel kbDatasetModel) {
if (kbDatasetModel.getId() != null && !DatasetAccess.owns(userId, kbDatasetModel.getId())) {
return ResultVo.fail("知识库不存在或无权访问");
}
Long embeddingId = kbDatasetModel.getEmbedding_mode_id();
if (embeddingId == null) {
embeddingId = kbDatasetModel.getId() == null ? 1002L : Db.queryLong("select embedding_mode_id from moss_kb_dataset where id=?", kbDatasetModel.getId());
kbDatasetModel.setEmbedding_mode_id(embeddingId);
}
if (!ModelAccess.canUse(userId, embeddingId) || Db.queryLong("select count(*) from moss_kb_model where id=? and model_type='EMBEDDING'", embeddingId) == 0) {
return ResultVo.fail("向量模型不存在或无权使用");
}
if (kbDatasetModel.getId() != null && !embeddingId.equals(Db.queryLong("select embedding_mode_id from moss_kb_dataset where id=?", kbDatasetModel.getId()))
&& Db.queryLong("select count(*) from moss_kb_paragraph where dataset_id=?", kbDatasetModel.getId()) > 0) {
return ResultVo.fail("已有文档的知识库不能直接切换向量空间,请新建知识库并重新导入文档");
}
ResultVo resultVo = new ResultVo();
TableResult<Kv> saveOrUpdate = Aop.get(MossKbDatasetDao.class).saveOrUpdate(userId, kbDatasetModel);
Kv data = saveOrUpdate.getData();
resultVo.setData(data);
return resultVo;
}
public ResultVo get(Long userId, Long id) {
ResultVo resultVo = new ResultVo();
TableInput tableInput = new TableInput();
if (userId != null && userId.equals(1L)) {
tableInput.set("id", id);
} else {
tableInput.set("id", id).set("user_id", userId);
}
TableResult<Row> result = ApiTable.get(MossKbTableNames.moss_kb_dataset, tableInput);
Kv kv = result.getData().toKv();
resultVo.setData(kv);
return resultVo;
}
public ResultVo list(Long userId) {
Row queryRecord = new Row();
if (userId.equals(1L)) {
} else {
queryRecord.set("user_id", userId);
}
String columns = "id,name,\"desc\",type,meta,user_id,embedding_mode_id,create_time,update_time";
List<Row> records = Db.find(MossKbDataset.tableName, columns, queryRecord);
List<Kv> kvs = RowUtils.toKv(records, false);
return ResultVo.ok(kvs);
}
public ResultVo delete(Long userId, Long id) {
if (!DatasetAccess.owns(userId, id)) {
return ResultVo.fail("知识库不存在或无权访问");
}
TableInput tableInput = null;
if (userId != null && userId.equals(1L)) {
tableInput = TableInput.by("id", id);
} else {
tableInput = TableInput.by("id", id).set("user_id", userId);
}
TableResult<Boolean> result = ApiTable.delete(MossKbTableNames.moss_kb_dataset, tableInput);
Row deleteRecord = Row.by("dataset_id", id);
Db.delete(MossKbTableNames.moss_kb_document, deleteRecord);
Db.delete(MossKbTableNames.moss_kb_paragraph, deleteRecord);
return new ResultVo(result.getData());
}
public ResultVo getApplicationByDatasetId(Long datasetId) {
return ResultVo.ok();
}
}
3. 数据访问层
MossKbDatasetDao 使用 TableInput 和 ApiTable 保存字段;权限及向量空间约束由上面的服务层校验。
package nexus.io.mosskb.dao;
import com.jfinal.kit.Kv;
import nexus.io.db.TableInput;
import nexus.io.db.TableResult;
import nexus.io.db.activerecord.Row;
import nexus.io.mosskb.constant.MossKbTableNames;
import nexus.io.mosskb.vo.KbDatasetModel;
import nexus.io.table.services.ApiTable;
public class MossKbDatasetDao {
public TableResult<Kv> saveOrUpdate(Long userId, KbDatasetModel model) {
TableInput record = new TableInput();
record.set("id", model.getId())
//
.set("name", model.getName()).set("desc",model.getDesc()).set("type",model.getType()).set("user_id", userId)
//
.set("embedding_mode_id", model.getEmbedding_mode_id());
return ApiTable.saveOrUpdate(MossKbTableNames.moss_kb_dataset, record);
}
public TableResult<Row> get(Long userId, Long id) {
TableInput tableInput = new TableInput();
tableInput.set("id",id).set("user_id",userId);
return ApiTable.get(MossKbTableNames.moss_kb_dataset, tableInput);
}
}
三、功能实现流程
获取知识库列表:用户发送 GET 请求到
/api/dataset/{pageNo}/{pageSize},控制器接收请求后调用MossKbDatasetService的page方法,通过ApiTable.page查询数据库并返回分页结果。创建知识库:用户发送 POST 请求到
/api/dataset,包含知识库的详细信息。控制器解析请求体,调用服务层的save方法,通过MossKbDatasetDao将新知识库保存到数据库。验证知识库:用户发送 GET 请求到
/api/valid/dataset/{size},系统返回该知识库是否存在的布尔值。获取单个知识库详情:用户发送 GET 请求到
/api/dataset/{id},控制器调用服务层的get方法,返回指定 ID 的知识库详情。获取知识库文档:用户发送 GET 请求到
/api/dataset/{id}/document/{pageNo}/{pageSize},系统返回该知识库下的文档列表,支持分页。删除知识库:用户发送 DELETE 请求到
/api/dataset/{id},控制器调用服务层的delete方法,删除指定 ID 的知识库,并同时删除相关联的文档和段落。创建并抓取站点知识库:用户发送 POST 请求到
/api/dataset/web,控制器转交MossKbWebDatasetService。服务先校验名称、描述、向量模型与根地址,再真实抓一次入口页面;地址不可用或取不到正文时直接返回错误。通过校验后写入type=1的知识库,立即返回详情,页面抓取与向量化在后台线程继续,文档逐个出现在文档列表里。
四、总结
通过上述接口设计和代码实现,我们构建了一个基本的知识库管理系统 MossKB。该系统支持知识库的创建、查询、分页展示、验证和删除等功能。控制器层负责处理 HTTP 请求,服务层封装了业务逻辑,数据访问层则负责与数据库的交互。
在实际开发中,可以根据需求进一步扩展系统功能,如增加权限控制、优化查询性能、集成全文搜索引擎等。此外,结合前端框架,可以构建一个完整的知识库管理平台,提升用户体验和系统的实用性。
通过系统化的设计和模块化的代码实现,MossKB 知识库管理功能具备了良好的可维护性和扩展性,能够满足不同场景下的知识管理需求。
修改、删除及模型空间约束
GET /api/dataset 返回当前账号可见的知识库列表。PUT /api/dataset/{id} 修改名称、说明及空知识库的向量模型;DELETE /api/dataset/{id} 删除知识库,均检查归属。
PUT /api/dataset/443309276048408576
Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json
{"name":"课程资料","desc":"更新说明","type":0,"embedding_mode_id":"1002"}
embedding_mode_id 是模型实例 ID,不能写成基础模型名称。可选择有使用权限的 EMBEDDING 实例,向量为 1024 维。已有分段的知识库不能直接切换模型;新模型应配合新知识库重新导入文档。
编辑请求允许只提交部分字段:没有提交的字段沿用原值,因此只改名称或说明时不会把 type 与 meta 清空,Web 站点知识库不会因为改个名字退化成通用型。
Web 站点知识库接口
| 方法 | 路径 | 内容 |
|---|---|---|
| POST | /api/dataset/web | 创建 Web 站点知识库,校验入口地址后由后台线程抓取 |
| PUT | /api/dataset/{id}/sync_web?sync_type=replace|complete | 替换同步或整体同步 |
| POST | /api/dataset/{id}/document/web | 按地址列表导入网页文档 |
| PUT | /api/dataset/{id}/document/{documentId}/sync | 重新抓取一个网页文档 |
| PUT | /api/dataset/{id}/document/_bach | 批量同步网页文档 |
抓取范围、正文选择器、分段规则与开关细节见 爬取网页数据。这些接口创建的文档 type 为 1,来源地址记录在文档 meta.source_url 里。
/api/valid/dataset/{size} 的参数是额度检查值,不是知识库 ID;它返回额度检查结果,不能代替资源归属和是否存在的检查。解析后提交分段须携带文件 id,使文档与上传文件关联。
