- platform/ → ont_platform/ rename
Python 내장 platform 모듈과 이름 충돌. numpy/scipy가 platform.machine() 호출 시
우리 패키지를 가져와 AttributeError. ont_platform으로 변경하고 pyproject.toml,
ont_platform/**, tests/** import 경로 모두 업데이트.
- ont_platform/config.py: lenient LLM builder 추가
LM Studio/vLLM 등 OpenAI-호환 로컬 서버가 임의 모델 식별자(예: deepseek-r1-distill-
qwen-7b)를 쓸 수 있도록 OntoCast의 OpenAIModel enum validation을 Pydantic
model_construct로 우회. ToolConfig() 생성 시 충돌을 막기 위해 LLM_MODEL_NAME을
잠시 비웠다가 lenient 인스턴스로 교체.
- ont_platform/api/deps.py: ToolBox 초기화를 asyncio.to_thread로 격리
LLMTool.create()가 내부에서 asyncio.run()을 부르는데 lifespan/테스트가 이미
async 컨텍스트라 이중 loop 충돌. 별도 스레드에서 sync 생성자 실행.
- 테스트 인프라 정비
* tests/integration/test_api_smoke.py: TestClient 구버전 starlette 호환을 위해
lifespan='off' 대신 app.router.lifespan_context = noop 패턴 적용.
* tests/unit/test_convert_document.py, test_select_ontology.py: ontocast.agent
__init__.py가 re-export한 함수가 서브모듈을 가리는 문제로 sys.modules에서
실제 모듈 객체 직접 추출.
* tests/e2e/conftest.py: .env 자동 로드 + provider별 skip 조건 (Ollama는
LLM_API_KEY 불필요).
* tests/e2e/test_phase0_full_pipeline.py: provider별 키 분기,
HDBSCAN 클러스터링이 동작하도록 fixture 페이로드 16문장으로 확장.
- vendored OntoCast 버그 수정 3건 (VENDORED_MODIFICATIONS.md 기록):
* agent/render_ontology.py: render_ontology_fresh()의 .format() 호출에 누락된
ontology_prefix 인자 추가 (Bootstrap 단계에서 KeyError: 'ontology_prefix').
* stategraph/node_factories.py: render_ontology/render_facts 노드의
state.model_copy(deep=True)로 budget_tracker가 deep-copy되어 root state의
BudgetTracker가 영원히 0인 채로 남던 버그 수정. 원본 인스턴스 공유로 변경.
- 문서 갱신
README.md (Phase 0.7 부분완료 + ont_platform 폴더 이름),
docs/phases/PHASE0_ACCEPTANCE_GATE.md (검증 이력 + Ollama/LM Studio 옵션),
.env.example (LM Studio/Ollama/OpenAI 세 옵션 명시).
검증
- unit + integration 26/26 통과.
- e2e (LM Studio + Qwen3-8B / DeepSeek-R1-Distill-Qwen-7B): 워크플로우 끝까지
실행 + 5번 LLM 호출 + LangGraph 전 노드 traceable 확인. 7-8B 로컬 모델은
strict structured output(Turtle RDF in JSON) 한계로 ontology/facts TTL 자동
생성 부분 성공. 클라우드 LLM 환경에서 재검증 필요.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Ontology Crawler Platform
향수 구독 플랫폼을 첫 사용 사례로 삼되, 차, 커피, 캔들, 디퓨저, 영양제, 선물, 패션 소품 같은 개인화 구독 추천 서비스에 재사용할 수 있는 범용 크롤러/온톨로지 기반 지식 DB MVP입니다.
핵심 아이디어
이 시스템은 웹에서 가져온 문장을 곧바로 사실로 저장하지 않습니다. 모든 정보는 Claim으로 저장됩니다.
subject: Product A
predicate: hasTopNote
object: Bergamot
source: OfficialSite
evidence_text: Top notes: Bergamot, Neroli
confidence: 0.95
추천 시스템은 원문 복제가 아니라, 출처, 근거, 신뢰도, 갱신일을 가진 온톨로지 매핑 지식을 사용합니다.
아키텍처
flowchart LR
A["URL Discovery"] --> B["Page Fetch"]
B --> C["HTML Clean"]
C --> D["Site Parser Plugin"]
D --> E["Extractor Provider"]
E --> F["Ontology Mapping"]
F --> G["Deduplication"]
G --> H["Confidence Scoring"]
H --> I["Knowledge DB"]
I --> J["Recommendation API"]
I --> K["Update Scheduling"]
구성 단위:
Project: 향수 구독, 차 구독, 선물 추천 같은 프로젝트 단위 설정Source: 공식몰, 마켓플레이스, 리뷰 사이트 같은 데이터 출처Page: 수집된 URL과 정제 텍스트 요약Entity: 상품, 브랜드, 노트, 무드, 계절, 상황 등 의미 객체Claim: 출처가 주장한 정보 단위Evidence: Claim의 근거 문장Relation: Entity 간 집계 관계ExtractionLog: 추출 방식, Provider, 로그
폴더 구조
crawler_platform/
app/
main.py
config/
core/
crawler/
extractor/
ontology/
database/
recommendation/
scheduler/
domains/
perfume/
tea/
coffee/
candle/
supplement/
gift/
api/
cli/
configs/
perfume_subscription.yaml
tests/
README.md
DB 스키마
초기 MVP는 SQLAlchemy ORM으로 SQLite와 PostgreSQL을 모두 지원합니다.
필수 테이블:
projects: 프로젝트 이름, 도메인, JSON 설정sources: 출처 타입, 신뢰도, robots 정책, rate limitpages: URL, fetch 상태, content hash, 정제 텍스트 요약entities: 범용 의미 객체attributes: Entity 속성relations: Entity 간 집계 관계claims: 출처가 주장한 subject-predicate-object 정보evidence: Claim 근거 텍스트extraction_logs: 추출 Provider와 로그crawl_jobs: 예약 수집 작업user_profiles: 추천 사용자user_preferences: 취향 구조feedback_logs: 추천 피드백
설치
pip install -r requirements.txt
playwright install chromium
정적 페이지는 requests + BeautifulSoup로 처리합니다. 동적 페이지가 필요하면 config에서 fetcher: playwright로 바꾸면 됩니다.
CLI 사용
DB 초기화:
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db init-db
향수 프로젝트 생성:
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db create-project --config configs/perfume_subscription.yaml
온톨로지 조회:
python -m crawler_platform.app.cli.main ontology --domain perfume
단일 URL 수집:
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db crawl-url \
--config configs/perfume_subscription.yaml \
--source official_brand_site \
--url https://example.com/perfume/product-page
네트워크 없이 로컬 샘플 HTML로 파이프라인을 확인할 수도 있습니다.
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db crawl-url \
--config configs/perfume_subscription.yaml \
--source official_brand_site \
--url tests/fixtures/sample_perfume.html
분석기 Provider를 바꿀 수도 있습니다. 기본값은 규칙 기반이며, AI Provider는 모델/API 키 또는 로컬 서버 설정이 필요합니다.
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db crawl-url \
--config configs/perfume_subscription.yaml \
--source official_brand_site \
--url tests/fixtures/sample_perfume.html \
--extractor-provider ollama \
--extractor-model llama3.1
지원 Provider:
rule_based: 정규식/키워드 기반 기본 분석기openai: OpenAI 호환 Chat Completions API,OPENAI_API_KEY와 모델 필요ollama: 로컬 Ollama, 기본 URLhttp://localhost:11434/api/chatlm_studio: LM Studio OpenAI 호환 서버, 기본 URLhttp://localhost:1234/v1/chat/completions
LM Studio 사용 순서:
- LM Studio에서
Developer또는 Local Server 화면을 엽니다. - OpenAI Compatible Server를 켭니다.
- 서버 주소가 보통
http://localhost:1234/v1인지 확인합니다. - 웹 UI의 Analyzer에서
LM Studio를 선택합니다. - Base URL은 비워두거나
http://localhost:1234/v1을 넣습니다. 연결 테스트로 모델 목록을 불러옵니다.- 모델이 자동 입력되면
수집 실행을 누릅니다.
Claim 확인:
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db claims --project perfume_subscription
추천 예시:
python -m crawler_platform.app.cli.main --db sqlite:///crawler_platform.db recommend \
--project perfume_subscription \
--target-type Perfume \
--preferences-json "{\"preferred_notes\":[\"Bergamot\",\"Musk\"],\"preferred_moods\":[\"Fresh\"],\"season_context\":\"Summer\"}"
FastAPI 실행
uvicorn crawler_platform.app.main:app --reload
브라우저에서 관리자 UI를 열 수 있습니다.
http://127.0.0.1:8000/
관리자 UI에서 가능한 작업:
- 프로젝트 config 경로로 프로젝트 생성
- Source 선택 후 URL 또는 로컬 HTML 샘플 수집
- 분석기 Provider 선택: Rule-based, OpenAI, Ollama, LM Studio
- 도메인 온톨로지, Entity, Claim 조회
- Claim 신뢰도와 사유 수동 수정
- Entity ID 기준 병합
- 추천용 태그 확인
- 사용자 취향 입력 후 추천 결과 테스트
주요 엔드포인트:
GET /healthGET /GET /projectsPOST /projectsGET /ontology/{domain}POST /crawlPOST /crawl-siteGET /projects/{project_name}/entitiesGET /projects/{project_name}/claimsPATCH /claims/{claim_id}/confidencePOST /entities/mergeGET /projects/{project_name}/recommendation-tagsPOST /recommend
사이트 순회 수집
단일 상품 URL뿐 아니라 Seed URL에서 시작해 같은 도메인의 링크를 따라가는 수집도 지원합니다.
Seed URL
→ robots 확인
→ 링크 추출
→ same-domain 필터
→ URL queue 저장
→ depth / max pages 제한
→ 각 페이지 fetch
→ 상품/브랜드/리뷰 페이지 판별
→ 분석
→ DB 저장
→ 다음 링크 반복
웹 UI에서는 Crawl site from seed를 사용합니다.
API 예시:
curl -X POST http://127.0.0.1:8000/crawl-site \
-H "Content-Type: application/json" \
-d '{
"config_path": "configs/perfume_subscription.yaml",
"source_name": "official_brand_site",
"url": "https://example-brand.com",
"extractor_provider": "rule_based",
"max_depth": 2,
"max_pages": 50,
"same_domain_only": true,
"analyze_page_types": ["product", "brand", "review"]
}'
주의: 검색 결과 페이지나 robots가 막는 페이지는 수집하지 않습니다. 그런 데이터는 공식 API Provider로 붙이는 방식이 맞습니다.
향수 도메인 MVP
기본 엔티티:
PerfumeBrandNoteAccordMoodSeasonOccasionReviewPriceProductPage
기본 관계:
hasBrandhasTopNotehasMiddleNotehasBaseNotehasAccordevokesMoodsuitableForSeasonsuitableForOccasionsimilarTosoldByhasPricehasReviewKeyword
규칙 기반 추출기는 Top notes, Middle notes, Base notes, 가격, 무드, 계절, 사용 상황, 리뷰 키워드를 우선 추출합니다.
확장 방법
새 도메인을 추가할 때는 다음을 추가하면 됩니다.
configs/{project}.yaml에domain,target_entities,fields,sources,ontology정의crawler_platform/app/core/ontology/definitions.py에 도메인 온톨로지 추가- 필요하면
crawler_platform/app/domains/{domain}/extractor.py에 도메인별 Extractor 구현 - 사이트별 HTML 구조가 특수하면
SiteParser를 구현하고ParserRegistry에 등록 - OpenAI, Ollama, LM Studio 등 AI 추출은
AIExtractor를 상속하는 Provider로 추가
컴플라이언스 설계
robots.txt확인 구조 포함- Source별 rate limit과 User-Agent 적용
- retry, timeout 고려
- 원문 전체 저장 대신
evidence_text와 정제 요약 중심 저장 - 상품 설명은 복제 저장보다 Claim, 태그, 요약, 근거 중심으로 사용
테스트
pytest
외부 테스트 러너가 없을 때는 기본 unittest 스모크 테스트를 실행할 수 있습니다.
python -m unittest tests.test_smoke_unittest -v
현재 테스트는 향수 규칙 기반 추출, config 로더, 로컬 HTML fetch 경로를 검증합니다.