24장 · 서비스 통합과 실행왜 서비스로 만들어야 하는가 — 고객은 터미널을 쓰지 않는다FastAPI — 파이썬 함수를 주소로 만든다세션과 대화 이력 — 브라우저마다 대화가 따로 있다SSE — 과정이 보이는 응답따라하기 — 서버 만들고 채팅 화면과 상담원 화면 열기따라하기 — 표준 질문 12개로 최종 검수돌아보기 — 아키텍처의 네 칸은 어디서 채워졌나정리와 체크리스트실습문제와 해답
24장. 서비스 통합과 실행
정리와 체크리스트
24장에서 배운 것
- 프론트엔드와 백엔드 — 화면(브라우저)과 처리하는 프로그램(서버)은 따로 돌고 HTTP로 대화합니다. 화면은 채팅 화면과 상담원 화면 둘입니다.
- FastAPI와 Uvicorn — 함수에
@app.get/@app.post를 붙여 주소로 만들고, Uvicorn이 서버를 띄웁니다. - 엔드포인트 — 주소 하나와 함수 하나의 짝. 우리 서버에는 고객 쪽 셋(화면, 점검, 채팅)과 상담원 쪽 다섯(
/admin,/api/actions,/api/approvals,POST /api/approvals/{action_id},/api/tickets)이 있습니다. - 웹의 승인 게이트 — 23장의 승인 게이트가 상담원 화면의 승인 버튼이 됩니다. 버튼은 같은
decide()를 부르고, 채팅 쪽과 상담원 쪽은 처리 기록 파일(actions.json)로 이어집니다. - 입력 검증 — 밖에서 들어오는 요청은 Pydantic 모델로 문 앞에서 검사합니다. 틀리면 422로 돌려보냅니다.
session_id— 화면이 만든 대화 이름표. 서버는 이름표별로 이력을 보관합니다. 새로고침하면 새 대화입니다.- SSE —
data: …한 줄이 이벤트 하나.delegate·answer·done세 종류로 과정을 흘려보냅니다. - 최종 검수 — 표준 질문 12개로 위임, 처리 결과, 검증 포인트를 맞춰 봅니다. 처리 기록을 비우고 서버를 다시 켠 뒤 시작합니다.
한눈에 보는 핵심
# haru-market 폴더 맨 위에서
$ python -m uvicorn app.main:app --reload --port 8000
# 채팅 화면: http://localhost:8000
# 상담원 화면: http://localhost:8000/admin 끄기: Ctrl + C
@app.post("/api/chat")
def chat(req: ChatRequest): # 입력은 문 앞에서 검사
history = histories.get(req.session_id, []) # 이 대화의 이력
def generate():
...
yield event({"type": "delegate", ...}) # 위임이 보일 때마다
yield event({"type": "answer", "text": ...}) # 최종 답변
histories[req.session_id] = new_messages # 이력 갱신
yield event({"type": "done"})
return StreamingResponse(generate(), media_type="text/event-stream", ...)
@app.post("/api/approvals/{action_id}")
def approve(action_id: str, body: Decision): # 상담원 화면의 승인·반려 버튼
return decide(action_id, approve=body.approve, by="상담원") # 23장과 같은 함수
| 층 | 파일 | 하는 일 |
|---|---|---|
| 화면 | app/frontend/index.html |
고객: 문의를 보내고 이벤트를 받아 그린다 |
| 화면 | app/frontend/admin.html |
상담원: 승인 대기를 승인·반려하고, 처리 내역과 넘겨받은 문의를 본다 |
| 서버 | app/main.py |
주소, 이력 보관, 이벤트 |
| 지휘 | haru_supervisor.py |
위임과 종합 |
| 전문가 | haru_agents.py |
주문조회, 정책안내, 요청처리 |
| 도구 | haru_tools.py, haru_actions.py |
조회(본인 확인, 마스킹)와 처리(코드의 판정, 처리 기록) |
체크리스트
- [ ] 프론트엔드와 백엔드가 무엇이고 어떻게 대화하는지 설명할 수 있다.
- [ ]
main.py를 어느 폴더에 만들고, 서버를 어느 위치에서 어떤 명령으로 띄우는지 안다. - [ ]
app.main:app의 세 조각이 각각 무엇을 가리키는지 말할 수 있다. - [ ] 우리 서버의 엔드포인트 여덟 개를 고객 쪽과 상담원 쪽으로 나눠 쓰임을 말할 수 있다.
- [ ] 채팅으로 요청한 주문 취소가 상담원 화면에 나타나는 까닭을 처리 기록 파일로 설명할 수 있다.
- [ ] 상담원 화면의 승인 버튼이 23장의 무엇에 해당하는지, 끝에서 어떤 함수가 불리는지 말할 수 있다.
- [ ] 모양이 틀린 요청이 왔을 때 어떤 일이 일어나고, 왜 비용이 나가지 않는지 설명할 수 있다.
- [ ] 새로고침하면 "그거"를 못 알아듣는 이유를
session_id로 설명할 수 있다. - [ ]
CustomerSession과session_id의 차이를 말할 수 있다. - [ ] 스트리밍이 걸리는 시간을 줄이지 않는데도 필요한 이유를 설명할 수 있다.
- [ ] 이벤트 세 종류와, 화면의 진행 표시가 어느 이벤트에서 오는지 안다.
- [ ] 서버를 끄면 대화는 사라지고 처리 기록은 남는 이유를 설명할 수 있다.
- [ ] 표준 질문 12개를 직접 넣어 보고, 질문마다 위임·처리 결과·검증 포인트를 확인했다.
- [ ] Q10에서 상담원 화면의 승인 버튼을 눌러
승인대기가승인완료로 바뀌는 것을 확인했다. - [ ] 프롬프트나 코드를 고친 뒤 열두 개를 전부 다시 돌려야 하는 이유를 설명할 수 있다.
- [ ] 아키텍처의 네 구성요소가 각각 어느 장에서 채워졌는지 말할 수 있다.
과정을 마치며 — 전체 체크리스트
스물네 장을 마친 지금, 아래를 말로 설명할 수 있는지 확인해 보세요.
- [ ] LLM만으로는 왜 "제 주문 어디 있어요?"에 답할 수 없는가. (도구)
- [ ] 모델이 도구를 "부른다"는 말이 실제로는 무슨 뜻인가. (5장)
- [ ] 정책을 모델의 짐작이 아니라 문서에서 가져오게 하려면 무엇이 필요한가. (지식)
- [ ] LLM은 앞 대화를 기억하지 않는데 어떻게 대화가 이어지는가. (기억)
- [ ] 에이전트 하나로 충분한 일과 여럿으로 나눠야 하는 일은 어떻게 가르는가. (오케스트레이션)
- [ ] 어떤 일을 AI에게 맡기고, 어떤 일을 코드가 하고, 어떤 일을 사람이 해야 하는가.
- [ ] "잘 만들었다"를 무엇으로 확인하는가.
한 줄 핵심
고르고 쓰는 일은 모델에게, 실행하고 보증하는 일은 코드에게, 되돌릴 수 없는 결정은 사람에게. 그리고 바꿀 때마다 같은 질문 열두 개로 다시 확인합니다.