codex를 활용한 업무 자동화(3)
[Codex
AX
]
이번에는 회의록 인덱싱 저장 기능을 한번 구현해 보겠다.
회의 녹취록 파일을 업로드 하면 이를 json파일로 Text화 하고, chromaDB에 인덱싱하여 저장한다. 이를 근거로 각 회의록 녹취를 RAG 검색을 하여 당시 회의에 대한 정보를 알 수 있다.
처음에는 chatGPT에 다음과 같이 요청을 했다.
회의 음성 녹음 파일을 텍스트화해 벡터DB에 저장하는 서비스가 사람들이 잘 쓸일이 있을 까?
GPT가 상당히 긴 답변을 주었지만 결론은 ‘잘 쓸 일이 있다’ 였다. 나는 이를 llama-index를 활용하고자 했다.
LlamaIndex기반으로 MVP구조를 만들어줘
🎯 MVP 목표 (중요)
이 정도만 되면 충분히 “쓸 만한 제품”입니다:
- 음성 업로드
- 자동 텍스트 변환
- 의미 기반 검색 (vector search)
- 질문하면 답변 (RAG)
- 클릭하면 해당 음성 구간 재생
🧱 전체 아키텍처
[Frontend]
↓
[API Server]
↓
───────────────
1. STT (Whisper)
2. Chunking
3. Embedding
4. Vector DB (pgvector)
5. LlamaIndex (Query Engine)
───────────────
⚙️ 기술 스택 (현실적인 추천)
- STT: OpenAI Whisper
- Index/RAG: LlamaIndex
- DB: pgvector
- Backend: FastAPI
- Frontend: React or Next.js
👉 이 조합이 가장 빠르고 안정적
나만 쓸 건데 굳이 FASTAPI 및 React를 쓸 필요가 있을 까?
그냥 내 PC에서만 쓸 건데… 기술 스택을 FASTAPI까지 써야 할까?
그러자 GPT는 기술 스택을 좀 더 단순화하여 제안했다.
⚙️ 기술 스택 (개인만 쓴다면…) STT: Whisper Index: LlamaIndex Vector DB: Chroma UI: Streamlit Backend: 없음
그래… 개인용이라면 이정도의 구성으로 충분할 것 같다.
이것을 이제 codex에 시켜보려 해. 이를 위한 프롬프트 초안을 만들어 줄 수 있어?
초안을 GPT가 만들었는데.. 상당히 길다
내 PC에서만 사용할 “회의 음성 녹음 파일 → 텍스트화 → 벡터 인덱싱 → 질의응답” 로컬 앱을 만들어줘.
목표:
- 개인용 로컬 MVP
- FastAPI 같은 서버는 사용하지 않음
- Streamlit UI 또는 CLI 기반으로 실행 가능
- 음성 파일을 업로드/선택하면 STT로 텍스트화
- 텍스트를 chunk로 나누고 LlamaIndex로 인덱싱
- 로컬 Vector DB는 Chroma 사용
- 질문을 입력하면 관련 회의 내용을 기반으로 답변
- 답변과 함께 근거 transcript 조각, 파일명, 가능하면 시작/종료 시간 표시
기술 스택:
- Python 3.11+
- LlamaIndex
... <중략>...
- Slack/Notion 연동 만들지 말 것
먼저 전체 설계를 간단히 설명한 뒤, 파일별로 코드를 작성해줘.
한 번에 모든 기능을 구현시키려 하면 Codex가 종종 맥락을 놓쳤다. 그래서 기능 단위를 잘게 나누고, 각 단계마다 실행 가능한 상태를 유지하는 방식으로 하려 한다.
이를 한꺼번에 던지면 제대로 일을 못할것 같어..
“맞아요. 한 번에 던지기보다 단계별 프롬프트로 쪼개는 게 훨씬 좋습니다.”
그러면서 GPT는 프롬프트를 단계별로 쪼개 주었다. 이를 이제 Codex에 하나씩 실행시켜 보도록 한다.
1단계: 프로젝트 뼈대
로컬 개인용 “회의 음성 → 텍스트 → LlamaIndex 검색” 앱을 만들 것이다.
먼저 전체 프로젝트 뼈대만 만들어줘.
조건:
- Python 3.11+
- Streamlit UI
- CLI 스크립트도 나중에 추가 예정
- FastAPI/Django 사용 금지
- ChromaDB 사용
- LlamaIndex 사용
- OpenAI API는 .env에서 읽기
이번 단계에서는:
1. 폴더 구조 제안
2. requirements.txt 작성
3. .env.example 작성
4. README.md 초안 작성
5. 빈 모듈 파일 생성용 코드 또는 파일 목록 제시
아직 STT, 인덱싱, 검색 로직은 구현하지 말고 TODO 주석만 넣어줘.
프로젝트 구조를 만들었다. codex에 python가상환경까지 만들게 하는 것은 시간이 좀 걸리므로(보안 문제 때문에 잘 안되었다) 내가 직접 해 버렸다. 터미널에서 다음과 같이 입력한다. 가상환경에 필수 라이브러리들을 설치하는 것이다.
python3 -m venv .venv
pip install -r requirements.txt
이제 다음 단계의 프롬프트를 실행시킨다
2차 프롬프트
이제 STT 모듈을 구현해줘.
대상 파일:
- src/stt.py
- ingest.py 일부
요구사항:
- audio 파일(mp3, wav, m4a)을 받아 텍스트로 변환
- OpenAI Whisper API 사용
- 결과를 data/transcripts/{원본파일명}.json 으로 저장
- JSON에는 source_file, text, segments 필드를 포함
- timestamp가 없으면 start/end는 null 처리
- 이미 transcript json이 있으면 재처리하지 않음
- 타입 힌트와 에러 처리 포함
이 단계에서 중간 검사를 하는 게 안전할 것 같다.
이 단계를 검사하려면 어떻게 할 까?
.venv/bin/python ingest.py data/audio/A301_중간.mp3
이 결과 data/transcripts/A301_중간.json 파일이 생성되면 검사는 OK
이제 3차 프롬프트 실행이다.
3차 프롬프트
이제 transcript JSON을 chunk로 나누는 기능을 구현해줘.
대상 파일:
- src/chunking.py
요구사항:
- transcript JSON을 읽어서 chunk 목록 생성
- chunk 크기 기본값 700 tokens
- overlap 기본값 100 tokens
- 각 chunk에는 다음 metadata 포함:
- source_file
- chunk_id
- start_time
- end_time
- timestamp가 없으면 start_time/end_time은 null
- LlamaIndex Document로 변환하는 함수 포함
이것도 결과 검사를 하면 좋겠다.
이 기능을 어떻게 검증하면 좋을까?
chunk 생성 테스트
python -B -c "import sys; sys.path.insert(0, 'src'); import chunking; chunks =
chunking.chunk_transcript_json('data/transcripts/A301_중간.json');
print(len(chunks)); print(chunks[0]['metadata']); print(chunks[0]['text'][:300])"
예상 출력은 대략 이런 형태입니다:
{'source_file': 'data/audio/A301_중간.mp3', 'chunk_id': 'A301_중간-0000',
'start_time': 0.0, 'end_time': 427.0}
저희는 지능형 영업관리를 위한 ...
LlamaIndex Document 변환 테스트
python -B -c "import sys; sys.path.insert(0, 'src'); import chunking; docs =
chunking.transcript_json_to_documents('data/transcripts/A301_중간.json');
print(len(docs)); print(type(docs[0]).__name__); print(docs[0].metadata)"
예상 출력:
Document
{'source_file': 'data/audio/A301_중간.mp3', 'chunk_id': 'A301_중간-0000',
'start_time': 0.0, 'end_time': 427.0}
chunk 크기를 바꿔 보고 싶으면:
python -B -c "import sys; sys.path.insert(0, 'src'); import chunking; chunks =
chunking.chunk_transcript_json('data/transcripts/A301_중간.json', chunk_size=300,
overlap=50); print(len(chunks)); [print(c['metadata']) for c in chunks]"
검사를 수행하고 결과를 눈으로 확인한 후 이제 4차 프롬프트를 실행시킨다.
4차 프롬프트
이제 LlamaIndex와 ChromaDB를 연결해서 벡터 인덱싱을 구현해줘.
대상 파일:
- src/indexer.py
요구사항:
- data/chroma/에 Chroma persistent index 저장
- embedding model은 text-embedding-3-small 기본값
- transcript chunk를 LlamaIndex Document로 받아 인덱싱
- 이미 인덱싱된 source_file은 중복 인덱싱하지 않도록 처리
- index 로드 함수와 index 생성/추가 함수 분리
- 타입 힌트와 에러 처리 포함
지금 단계에서 따로 검사할 부분은 없어 보인다. 다음 프롬프트를 실행시킨다
5차 프롬프트
이제 질문 검색 기능을 구현해줘.
대상 파일:
- src/query.py
- ask.py
요구사항:
- 저장된 Chroma index를 로드
- 사용자의 질문을 받아 LlamaIndex query engine으로 답변 생성
- LLM 기본값은 gpt-4o-mini
- 답변과 source nodes 3~5개를 출력
- source node마다 text, source_file, start_time, end_time 표시
- CLI 실행 예: python ask.py “지난 회의에서 결정된 내용은?”
테스트로 CLI실행을 해 본다.
이제 6차 프롬프트를 실행한다
6차 프롬프트
이제 Streamlit UI를 구현해줘.
대상 파일:
- app.py
요구사항:
- data/audio/ 폴더의 mp3, wav, m4a 파일 목록 표시
- 파일 선택 후 “텍스트화 및 인덱싱” 버튼
- 처리 상태 표시
- 질문 입력창
- 답변 출력
- 관련 transcript 조각 3~5개 표시
- 각 조각에는 source_file, start_time, end_time, text 표시
- FastAPI 같은 서버는 사용하지 말 것
여기까지 만들었다면, 다음과 비슷한 구조로 되어있을 것이다.
.
├── README.md
├── app.py
├── ask.py
├── data
│ ├── audio
│ │ └── A301_중간.mp3
│ ├── chroma
│ │ └── chroma.sqlite3
│ └── transcripts
│ └── A301_중간.json
├── ingest.py
├── requirements.txt
├── src
...<중략>
├── storage
│ └── chroma
└── tests
19 directories, 46 files
녹음 파일은 data디렉토리 및 audio 및에 넣어놓고 streamlit 을 실행시키면 로컬에서 이를 볼 수 있다.
> streamlit run app.py
화면은 잘 뜬다. 다른 녹음 파일을 audio폴더에 복사하고 페이지를 리로딩하면 Audio파일 목록에 반영된다.

그런데 A304_중간.mp3 파일을 ‘텍스트화 및 인덱싱’을 한 후 Search를 하는데 Search대상이 계속 A301_중간.mp3의 내용이 검색되는 오류가 있었다. 이의 수정을 보강하자.
mp3 파일을 새로 data/audio파일에 추가하고 이를 인덱싱을 app.py를 통해 했어.. 그리고 질문을 던졌는데 계속 transcripts는 예전 것을 읽는 것 같어… 이를 선택된 오디오 파일에 맞게 transcripts를 변경하여 검색하는 걸로 바꿔줘
실행해 보니 이제 제대로 검색대상이 바뀌어 나온다.

여기서 이제 코드의 마무리 정리를 위한 마지막 7차 프롬프트를 실행한다
7차 프롬프트
전체 코드를 점검하고 실행 가능하게 수정해줘.
확인할 것:
- import 경로 오류
- requirements 누락
- .env 로딩
- data/audio, data/transcripts, data/chroma 폴더 자동 생성
- 중복 인덱싱 방지
- README 실행 방법 업데이트
가능하면 최소 테스트용 mock transcript도 하나 추가해줘.
이렇게 해서 나만의 회의 녹취록 관리 시스템 하나를 만들었다.
여기에서 눈여겨 볼 점은 하나의 프롬프트에 너무 많은 요구사항을 구겨넣기 보다는 단계별로 나누어 넣고 중간중간에 확인이 가능한 부분은 실행하면서 잘 진행되는지 여부를 체크해야 한다는 것이다.