위시켓 공고: "LLM 기반 자연어 리드 분석 및 Tool Calling 워크플로 구축" (마감 2026-05-27)
본 저장소는 위 공고의 사전 검증 질문에 동작하는 코드 (GitHub 링크) 로 답하기 위해 1–2일에 직접 구현한 미니 오케스트레이터 데모입니다.
자연어 한 줄 → 정형 리드 추출 → 부족하면 자동으로 되묻기 → 일사량 공공 API + Mock 매칭 API + Dummy RAG 인터페이스를 LangChain Tool Calling 오케스트레이터 가 골라 호출 → 한 화면에 챗봇 + 리포트 패널.
핵심 가치 한 줄: 리뷰어가 저장소를 클론해서 streamlit run app.py 만 실행해도, 코드를 한 줄도 읽지 않고 "오케스트레이터가 Tool 을 어떻게 부르는가 / 외부 API 실패 시 어떻게 부드럽게 우회하는가" 를 눈으로 확인할 수 있다.
OpenAI 키나 공공 API 키가 없어도 다음 항목이 즉시 검증 됩니다 (저장소 클론 직후):
pytest tests/test_fallback.py -v # 7개 fallback 시나리오 통과 (키 불필요)
python -X utf8 scripts/keyless_demo.py # 모든 Tool 의 직접 호출 + Mock + fallback 시연
streamlit run app.py # UI 셸 + 사이드바 D1~D15 컨트롤 + 친절한 키 누락 안내OpenAI 키만 .env 에 추가되면 채팅 입력 → 리드 추출 → 오케스트레이터 → 리포트의 전체 종단 흐름이 즉시 동작 합니다. 추가 코드 변경은 필요 없습니다.
상단 🔑 안내 (D6) + 🌤 fallback 안내 (D7), 좌측 사이드바의 모델 선택 (D10) · 강제 타임아웃 토글 (D8) · 한국어 예시 입력 버튼 3종 (D4) · 대화 초기화 (D13), 우측 리포트 패널 placeholder.
강제 타임아웃 토글이 빨갛게 활성화 (FORCE_SOLAR_TIMEOUT=1 환경변수와 test_fallback.py 의 같은 플래그를 공유). 빌드 로그 expander 가 펼쳐져 실제 커밋 13개를 노출.
uvicorn api:app --reload 한 줄로 기동되는 HTTP 표면. meta 그룹의 GET /health, agent 그룹의 POST /chat, 그리고 models.py 에서 자동 추출된 10개 Pydantic 스키마 (AgentResponse, Lead, MatchResult, RadiationData, Installer, ToolResult, ChatRequest, HealthResponse, HTTPValidationError, ValidationError).
엔드포인트 설명 (한국어) + 응답 kind 별 처리법 (clarification / report / error) + 한국어 example body ("서울 강남구에 100kW 상업용 태양광 검토 중인데 예산은 1억 5천") + AgentResponse 전체 구조의 응답 예시. 공고 산출물의 "시스템 아키텍처 및 API 명세서" 요구가 이 한 페이지로 자동 충족됩니다.
키 발급 후 추가로 캡처할 3장 (success / clarification / fallback 의 실제 채팅 흐름) 은
docs/screenshots/에 같은 명명 규칙으로 추가하시면 README 의 위 4장 아래에 그대로 이어 붙으면 됩니다.
[사용자] 서울 강남구에 100kW 상업용 태양광 검토 중이에요
↓ lead_extract (Pydantic 구조화 추출)
{ location: "서울 강남구", capacity_kW: 100, installation_type: "commercial", budget_KRW: null }
↓ 오케스트레이터가 누락 감지 → Clarification 분기
[에이전트] 예상 투자 예산 범위를 알려주시겠어요? 예: '1억~1.5억'
↓
[사용자] 1억 5천 정도
↓ 4가지 필드 모두 충족 → Tool 호출
├─ solar_radiation(서울 강남구) → 공공 일사량 API (3초 타임아웃)
├─ match_installers(리드 정보) → Mock 매칭 API
└─ rag_knowledge → Dummy (호출 안 함, 인터페이스만 존재)
↓
[리포트 패널] 추천 시공사 3건 + 예상 발전량/회수기간 + 일사량 카드
API 호출이 3초 안에 응답하지 않으면 자동으로 연평균 추정값으로 우회하고, 에이전트가 “현재 실시간 데이터를 가져오지 못했습니다. 연평균값으로 임시 추정해 안내드립니다.” 라고 한국어로 명시적으로 알립니다. 조용한 대체값이 아니라 사용자에게 보이는 graceful degradation — 본 공고의 비기능 요구사항을 그대로 시연합니다.
| 공고 요구사항 | 데모에서 증명하는 것 | 파일 |
|---|---|---|
| 모듈 1: 자연어 → JSON 구조화 | with_structured_output(Lead, method="json_schema", strict=True) |
tools/lead_extract.py |
| 모듈 1: Clarification 자동 생성 | Lead.required_missing() + 오케스트레이터 분기 |
agent.py (respond()) |
| 모듈 3: Tool Calling 오케스트레이터 | from langchain.agents import create_agent (LangChain v1) |
agent.py (get_agent()) |
| 모듈 3: 당사 API 연동 | Mock 매칭 API — 본 수주 시 단일 파일 본문만 교체 | tools/match_installers.py |
| 모듈 3: 공공 API 연동 | 기상청 ASOS 시간자료 (data.go.kr #15057210) | tools/solar_radiation.py |
| 모듈 3: Fallback 예외 처리 | 3초 phase-explicit Timeout + is_fallback 구조화 플래그 + LLM 한국어 안내 |
tools/solar_radiation.py + prompts.py |
| 모듈 3: RAG Dummy Tool 세팅 | @tool 데코레이션된 NotImplementedError 인터페이스 |
tools/rag_knowledge.py |
| 모듈 4: Streamlit 챗봇 + 리포트 | 한 화면 2 컬럼 + 사이드바 차별화 패널 (D1~D15) | app.py |
| FastAPI 서비스 레이어 | 동일 오케스트레이터를 HTTP 로 노출 (POST /chat, GET /health) + /openapi.json 자동 생성 |
api.py |
| 산출물: 시스템 아키텍처 및 API 명세서 | /docs Swagger UI + /openapi.json 자동 생성 + .planning/research/{STACK,ARCHITECTURE,PITFALLS}.md |
/openapi.json, .planning/research/ |
git clone https://github.com/Taek-D/SolarLeadAgent.git
cd SolarLeadAgent
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# .env 파일을 열어 OPENAI_API_KEY 와 (선택) DATA_GO_KR_KEY 를 채워주세요
streamlit run app.py # 데모 UI (포트 8501)
# 또는
uvicorn api:app --reload # FastAPI 서버 (포트 8000) → http://localhost:8000/docsgit clone https://github.com/Taek-D/SolarLeadAgent.git
cd SolarLeadAgent
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
Copy-Item .env.example .env
# .env 파일을 열어 OPENAI_API_KEY 와 (선택) DATA_GO_KR_KEY 를 채워주세요
streamlit run app.py # 데모 UI (포트 8501)
# 또는
uvicorn api:app --reload # FastAPI 서버 (포트 8000) → http://localhost:8000/docs- OPENAI_API_KEY (필수) — https://platform.openai.com/api-keys
- DATA_GO_KR_KEY (선택) — https://www.data.go.kr/
- 없으면 일사량은 자동으로 fallback 경로로 동작합니다. 이는 의도된 graceful degradation 시연입니다.
- 발급 시 Decoding 폼 키를 그대로
.env에 붙여넣으세요 (인코딩 폼을 넣으면 httpx 가 이중 인코딩해SERVICE KEY IS NOT REGISTERED가 납니다 — 흔한 함정입니다).
pytest # 전체 스위트
pytest tests/test_fallback.py -v # 타임아웃/Fallback 경로만
pytest tests/test_lead_extract.py -v # 한국어 숫자 추출 회귀 (OPENAI_API_KEY 필요)OPENAI_API_KEY 또는 langchain 의존성이 없는 환경에서도 pytest 가 그대로 통과합니다 — 보안 자료 의존 테스트는 자동으로 skipif 처리됩니다.
| ID | 기능 | 역할 |
|---|---|---|
| D1 | Tool 호출 트레이스 | 채팅 메시지 옆 expander 에 호출된 Tool 이름과 JSON 페이로드 실시간 표시 |
| D2 | Fallback 배지 | is_fallback=True 일 때 리포트 패널 상단에 ⚠ 임시 추정 (연평균값) 배지 |
| D3 | 추출 JSON 카드 | 오케스트레이터가 파싱한 Lead 인스턴스를 JSON 으로 표시 |
| D4 | 예시 입력 버튼 | 3종의 한국어 시연 프롬프트 — 콜드스타트 즉시 해결 |
| D6 | OPENAI_API_KEY 누락 시 친절한 한국어 안내 | 빨간 에러 박스로 어디에 키를 넣을지 안내 |
| D7 | DATA_GO_KR_KEY 누락 시 fallback 안내 | 일사량이 fallback 으로 동작한다는 정보 박스 |
| D8 | 강제 타임아웃 토글 | 사이드바 토글 ON → fallback 경로 즉시 시연 (테스트와 같은 FORCE_SOLAR_TIMEOUT 환경변수) |
| D10 | 모델 스위치 | gpt-4o-mini ↔ gpt-4.1 사이드바 선택 |
| D11 | 빌드 로그 | git log --oneline -20 을 사이드바 expander 에 노출 |
| D13 | 대화 초기화 | session_state 클리어 + 즉시 리런 |
| D15 | 한국어 UI | 페이지 제목, 헤더, 버튼, placeholder 모두 한국어 |
┌─────────────────────────────────┐
│ Streamlit UI (app.py) │
│ chat_input / chat_message │
│ + 리포트 패널 (시공사 / 지표) │
│ + 사이드바 (D1~D15) │
└──────────────┬──────────────────┘
│ session_state
┌──────────────▼──────────────────┐
│ Orchestrator (agent.py) │
│ pre-agent lead_extract │
│ + Clarification 분기 │
│ + 결정론적 Tool 순서 │
│ (LangChain v1 create_agent) │
└───┬────────┬────────┬───────────┘
│ │ │
┌────────────▼─┐ ┌───▼──────┐ ┌▼─────────────┐
│ lead_extract │ │ solar_ │ │ match_ │
│ (Pydantic) │ │ radiation│ │ installers │
│ pure fn │ │ httpx + │ │ (Mock) │
│ │ │ fallback │ │ │
└──────────────┘ └──────────┘ └──────────────┘
┌──────────────────────────────┐
│ rag_knowledge (Dummy @tool) │
│ NotImplementedError 반환 │
└──────────────────────────────┘
핵심 설계 원칙: Tool 인터페이스 계층 분리. 오케스트레이터 로직을 건드리지 않고 각 Tool 내부만 교체 가능. 본 수주 시 Mock/Dummy Tool 본문만 클라이언트의 실제 API 호출로 바꾸면 됩니다. 시그니처와 반환 Pydantic 타입은 그대로 유지됩니다.
| 항목 | 이유 |
|---|---|
추가됨 — api.py 가 동일 오케스트레이터를 POST /chat 으로 노출. /openapi.json 으로 명세 자동 생성. |
|
| 실제 시공사 매칭 / 점수화 알고리즘 | Mock 자체가 아키텍처 제안입니다 — 공고도 클라이언트가 매칭 엔진을 API 로 제공합니다. 본 수주 시 match_installers.py 본문을 httpx.post(client_api_url, json=lead.model_dump()) 한 줄로 교체합니다. |
| 실제 RAG 지식베이스 | rag_knowledge.py 의 NotImplementedError 가 인터페이스 합의입니다. 클라이언트 사내 지식베이스 / 벡터스토어 연결도 같은 단일 파일 교체로 끝납니다. |
| 인증 / 다중 사용자 / 영속화 | 단일 세션 ephemeral 데모입니다. 인증 흐름은 오케스트레이터 평가를 흐리게 합니다. |
| Docker / 컨테이너화 | 리뷰어 입장에서 pip install -r requirements.txt 가 Docker 보다 빠릅니다. |
비동기 httpx.AsyncClient |
Streamlit 의 Tornado 이벤트 루프와 충돌합니다 (nest_asyncio 우회는 코드 스멜). 본 데모는 의도적으로 sync httpx 를 씁니다. 본 수주 시 FastAPI 뒤로 옮기면서 자연스럽게 비동기로 전환합니다. |
본 공고 기술 스택 (Python · FastAPI · LangChain · OpenAI API · Streamlit) 으로 자연어 리드 추출 → Tool Calling 오케스트레이터 → 공공 API 연동 + 비기능적 Fallback → Streamlit 리포트 + FastAPI HTTP 노출 까지의 전체 흐름을 미니 데모로 직접 구현해 공개했습니다.
저장소를 클론한 직후 (OpenAI 키 없이) pytest 가 그대로 통과하고, streamlit run app.py 또는 uvicorn api:app --reload 로 UI / API 서버가 즉시 기동합니다. OpenAI 키만 .env 에 추가되면 종단 흐름 전체가 동작합니다 — 추가 코드 변경 없이.
데모의 Mock API 경계 (match_installers, rag_knowledge) 를 당사 실제 매칭/수익 API + 사내 지식베이스로 교체하면 본 과업으로 그대로 확장 가능합니다 — 시그니처와 오케스트레이터 호출부는 손대지 않습니다. /openapi.json (api.py 가 자동 생성) 이 산출물 요구 사항의 "API 명세서" 역할을 합니다.
| 영역 | 기술 | 핵심 사용 |
|---|---|---|
| 언어 | Python 3.12+ | 단일 언어로 백엔드 + AI + UI |
| 오케스트레이션 | LangChain v1 (langchain>=1.3, langgraph>=1.2) |
create_agent (LangGraph 런타임 백엔드) |
| LLM | OpenAI (gpt-4o-mini 기본, gpt-4.1 선택) |
with_structured_output(strict=True) |
| 구조화 추출 | Pydantic v2 (pydantic>=2.13) |
타입 안전 JSON 추출 (strict-mode 호환 스키마) |
| HTTP | httpx (sync Client, >=0.28) |
공공 API 호출 + phase-explicit Timeout |
| HTTP 서버 | FastAPI (>=0.115) + uvicorn |
POST /chat + /docs Swagger UI + 자동 OpenAPI 명세 |
| UI | Streamlit (>=1.55) |
챗봇 + 리포트 한 화면 |
| 테스트 | pytest + pytest-httpx | 추출 회귀 + Fallback 회귀 |
전체 핀은 requirements.txt 참고.
feat(04): Streamlit chat+report UI with D1 tool trace + D2 fallback badge + D3 extraction card
feat(03): create_agent orchestrator with clarification + narrated fallback + 4 fake-injected tests
feat(02): lead_extract pure fn, solar_radiation sync httpx + structured fallback, pytest 7/7 green
feat(01): bootstrap, Pydantic schemas, mock installer + dummy RAG tools
docs: create roadmap (5 phases)
docs: define v1 requirements
docs: project research (STACK + FEATURES + ARCHITECTURE + PITFALLS + SUMMARY)
docs: initialize project
chore: add project config
전체 로그: git log --oneline (사이드바 D11 expander 에서도 같은 내용을 볼 수 있습니다).
데모용 포트폴리오 코드. 위시켓 지원자 Taek-D.



