- openAI API를 활용해 문서를 작성하는 프로젝트를 진행하는 과정에서 파일을 분석해 해당 내용을 기반으로 작성하는 부분이 있었음
- 일반적인 대화형 및 이미지 첨부는 해봤는데, 파일 첨부는 처음이기도 했고 생각보다 삽질을 하는 바람에 기록을 남김 허헣
- 참고로 구현환경은 nest.js임
파일 생성(files.create)
- 일반적으로 우리가 사용하는 chatGPT UI와는 다르게, 파일 업로드에는 openai platform의 storage에 파일을 추가하는 과정이 불가피함.
- 이미지의 경우 url로 직접 넘길 수도 있지만, 파일은 그 방식이 불가함.
- 보통 예시로 많이 보이는 fs를 통해 파일을 직접 업로드 하는 방식이 아니라, cloud 등에 있는 파일의 url을 통해 업로드 하는 방법을 사용함.
- openAI sdk의 files.create를 사용했고, 해당 함수는 결과값으로 업로드된 file의 정보를 반환함.
async uploadFile(fileDetail: InformationFileDTO): Promise<string | null> {
try {
const response = await this.httpService.axiosRef.get(fileDetail.urls[0], {
responseType: 'arraybuffer',
});
const fileExtension = fileDetail.urls[0].split('.').pop() || 'txt';
let fileType = ''
switch (fileExtension) {
case 'docx': {
fileType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
break;
}
case 'doc': {
fileType = 'application/msword';
break;
}
default: {
fileType = 'application/pdf';
}
}
console.log(`확장자 포함 파일명: ${fileDetail.name}.${fileExtension}, MIME 타입: ${fileType}`);
const file = new File([response.data], `${fileDetail.name}.${fileExtension}`, { type: fileType });
const uploaded = await this.openai.files.create({
file: file,
purpose: 'assistants'
})
return uploaded.id;
} catch (error) {
console.error(`파일 처리 중 오류 발생: ${error.message}`);
return null;
}
}
- nest.js 내당 httpModule을 사용해서 파일을 불러왔음
- 파일을 File 객체로 생성하고 있는데, 이는 브라우저에서 주로 사용하는 방법인거? 아는데 이 방법으로만 성공해서 일단 이렇게 두었음.. 향후 개선의 여지가 아주 많다..
+) fs를 활용한 임시 파일 저장 및 buffer를 사용한 stream 방식 등도 시도해 봤지만, openai의 Uploadable 형식에 맞지 않아 실패함
- 데이터 DTO에 정의된 name에 확장자 명이 포함이 안되어있었는데, files.create에서 파일에 확장자 명이 없으면 Openai Storage에서 mimeType을 인식하지 못한다.(None으로 나옴)
- 그러니 꼭 파일 확장자명이 포함된 이름을 사용할것!
- 추가적으로 openAI API로 hwp, hwpx 파일을 업로드해 사용하는 것은 🚧불가능🚧 하다.
=> 혹시라도 방법을 찾고있다면 고생은 이미 제가 해봤으니 조속히 다른 길을 찾으시길..
파일 첨부해서 질문하기
- 파일 첨부의 경우 일단 chat.completion을 기반으로 했음
- 이 경우 우리가 사용하는 chatGPT UI에서 파일을 첨부하는 방식과 동일하다고 보면 됨.
const messageContents: ChatCompletionContentPart[] = [
{
type: 'text',
text: promptTemplate.fileAnalayzeTemplate,
},
];
const fileData = await Promise.all(
bmService.files.map(async (file, idx) => {
const fileDetail = file as unknown as InformationFileDTO;
const uploadedFileId = await this.uploadFile(fileDetail);
if (uploadedFileId) {
console.log(
`------------파일 업로드 완료: ${fileDetail.name}------------`,
);
messageContents.push({
type: 'file',
file: {
file_id: uploadedFileId,
},
});
} else {
console.warn(`파일 처리 실패: ${fileDetail.name}`);
}
}),
);
await this.openai.vectorStores.create({
metadata: {},
});
const result = await this.openai.chat.completions.create({
model: 'gpt-4.1-mini',
messages: [
{
role: 'user',
content: messageContents,
},
],
temperature: 0.3,
});
- message content에 type을 file로 해서, 이전에 획득한 업로드 파일의 id를 넣어주면 된다.
- 모델은 가성비 목적으로 4.1-mini를 사용했으며, 큰 의미는 없다.
- 추가적으로 파일과 프롬프트가 길 경우 RAG 방식을 고려해 볼 수 있겠는데, 그 경우에는 o3와 같은 추론 모델이 강력할 것으로 보이긴 함.
- 또는 Agent를 기반으로 구현하는 방법도 고려해볼 수 있겠음.