最近开发的系统的功能之一是要把前端上传的文件保存在MinIO的同时,还要用OCR识别存入ES和LangChain的知识库。LangChain在服务器上搭建好了,FastAPI也准备就绪,就差SpringBoot调用接口上传了,这个问题足足卡了我三四天,随着本地知识库的兴起,遇到这种问题的人应该会有很多,故写此文以供参考。
已知条件
使用Swagger调试FastAPI接口curl的传参结构为
rust
curl -X 'POST' \
'http://xxx/knowledge_base/upload_docs' \
-H 'accept: application/json' \
-H 'Content-Type: multipart/form-data' \
-F 'to_vector_store=true' \
-F 'override=true' \
-F 'not_refresh_vs_cache=false' \
-F 'chunk_size=1000' \
-F 'chunk_overlap=50' \
-F 'zh_title_enhance=true' \
-F 'files=@XXXX.txt;type=text/plain' \
-F 'knowledge_base_name=ganlan_guichengguifan' \
-F 'docs='
参数释义:
- to_vector_store指上传文件后是否进行向量化。
- override指是否覆盖已有文件。
- not_refresh_vs_cache指是否暂不保存向量库(用于FAISS)。
- chunk_size指知识库中单段文本最大长度。
- chunk_overlap指知识库中相邻文本重合长度。
- zh_title_enhance指是否开启中文标题加强。
- files指咱们要上传的文件。
- docs指自定义的docs,可以为空。
因为传参结构是固定的,所以在SpringBoot中创建对象UploadKnowledgeEntity
java
import lombok.Data;
import org.springframework.core.io.FileSystemResource;
@Data
public class UploadKnowledgeEntity {
/**
* 上传文件后是否进行向量化
*/
private boolean to_vector_store;
/**
* 覆盖已有文件
*/
private boolean override;
/**
* 暂不保存向量库(用于FAISS)
*/
private boolean not_refresh_vs_cache;
/**
* 知识库中单段文本最大长度
*/
private int chunk_size;
/**
* 知识库中相邻文本重合长度
*/
private int chunk_overlap;
/**
* 是否开启中文标题加强
*/
private boolean zh_title_enhance;
/**
* 知识库名称
*/
private String knowledge_base_name;
/**
* 文件
*/
private FileSystemResource files;
}
注意看UploadKnowledgeEntity类中的文件的类型FileSystemResource,这个参数是SpringBoot发出的请求能否被FastAPI接受的关键,原因如下:
查看LangChain的upload_docs方法
python
def upload_docs(
files: List[UploadFile] = File(..., description="上传文件,支持多文件"),
knowledge_base_name: str = Form(..., description="知识库名称", examples=["samples"]),
override: bool = Form(False, description="覆盖已有文件"),
to_vector_store: bool = Form(True, description="上传文件后是否进行向量化"),
chunk_size: int = Form(CHUNK_SIZE, description="知识库中单段文本最大长度"),
chunk_overlap: int = Form(OVERLAP_SIZE, description="知识库中相邻文本重合长度"),
zh_title_enhance: bool = Form(ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),
docs: Json = Form({}, description="自定义的docs,需要转为json字符串",
examples=[{"test.txt": [Document(page_content="custom doc")]}]),
not_refresh_vs_cache: bool = Form(False, description="暂不保存向量库(用于FAISS)"),
可以看到API的files类型是UploadFile
,Python的UploadFile
主要用于处理文件上传的场景。在使用requests
库进行文件上传时,可以通过files
参数来指定文件的位置和名称。
因为Java的FileSystemResource
主要用于获取文件系统里面的资源。它既可以作为文件处理,也可以作为URL处理。
两者都可以用于处理文件上传和文件系统资源的获取。
这就是为什么UploadKnowledgeEntity实体里的files文件类型不是File,不是MultipartFile,不是File.getBytes()的原因,因为我他妈这几天都已经试过了,接口全都不认哈哈哈。
有了上述已知条件后,可求得核心代码如下:
java
/**
* 发送post请求
* @param url
* @param multipartFile
* @param fileType
* @return
* @throws Exception
*/
public static String doPostJson(String url,MultipartFile multipartFile,String fileType) throws Exception {
UploadKnowledgeEntity knowledgeEntity=new UploadKnowledgeEntity();
knowledgeEntity.setOverride(false);
knowledgeEntity.setTo_vector_store(true);
knowledgeEntity.setNot_refresh_vs_cache(false);
knowledgeEntity.setChunk_size(1000);
knowledgeEntity.setChunk_overlap(50);
knowledgeEntity.setZh_title_enhance(true);
knowledgeEntity.setKnowledge_base_name("XXX");
File file=multipartFileToFile(multipartFile);
FileSystemResource fileSystemResource=new FileSystemResource(file);
knowledgeEntity.setFiles(fileSystemResource);
try {
// 组装请求信息
MultiValueMap<String,Object> param=new LinkedMultiValueMap<>();
Map<String,Object> map=entityToMap(knowledgeEntity);
for (Map.Entry<String, Object> entry : map.entrySet()) {
param.add(entry.getKey(), entry.getValue());
}
HttpEntity<MultiValueMap<String,Object> > httpEntity=new HttpEntity<>(param);
RestTemplate restTemplate=new RestTemplate();
KnowledgeAnswer response=restTemplate.postForObject(url,httpEntity,KnowledgeAnswer.class);
return "文件上传成功";
} catch (Exception e) {
throw new RuntimeException("[发送POST请求错误:]" + e.getMessage());
}
}
测试文件上传,响应内容:
json
{
"success": true,
"message": "文件上传成功",
"code": 200,
"result": "文件上传成功",
"timestamp": 1702541585135
}