Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,20 @@ Amazon Bedrock 기반(필터 GPT-6.1 Sol, 요약 Claude) · AWS 위에서 CDK로

### 핵심 구성 요소

`app/` 아래 코드는 세 계층으로 나뉩니다. `domain/`은 입출력이 없는 규칙, `application/`은
`application/ports.py`의 인터페이스에 대고 쓴 서비스, `adapters/`는 Bedrock·AWS·웹·템플릿과
말하는 코드입니다. 의존은 안쪽으로만 향하고, `main.py`가 계층을 연결하며,
`tests/test_architecture.py`가 이 규칙을 검사합니다([design.md §3](./docs/design.md#3-저장소-구조)).

| 모듈 | 책임 |
| --- | --- |
| `feed_parser.py` | RSS 파싱 + HTML 스크레이핑(BeautifulSoup4), 소스별 헬스 추적 |
| `summarizer.py` | 콘텐츠 게이트 → 관련성 필터 → 랭킹/캡 → 요약까지, 모두 Bedrock으로 |
| `model_factory.py` | Bedrock 모델 역량 레지스트리 + LangChain 채팅 모델 생성, 비용 귀속 프로파일 |
| `quality_metrics.py` | 결정적 출력 품질 루브릭 (순수 함수) |
| `eval_metrics.py` | 필터 점수 지표 (순수 함수) |
| `newsletter_renderer.py` | Jinja2 기반 HTML 생성(반응형, 다크모드, 현지화, 크기 검사) |
| `aws_helpers.py` | S3, SES, SNS, SSM, Batch 연동 |
| `adapters/feeds.py` | RSS 파싱 + HTML 스크레이핑(BeautifulSoup4), 소스별 헬스 추적 |
| `application/summarize.py` | 콘텐츠 게이트 → 관련성 필터 → 랭킹/캡 → 요약까지, 모두 Bedrock으로 |
| `adapters/bedrock.py` | Bedrock 모델 역량 레지스트리 + LangChain 채팅 모델 생성, 비용 귀속 프로파일 |
| `domain/quality_metrics.py` | 결정적 출력 품질 루브릭 (순수 함수) |
| `domain/eval_metrics.py` | 필터 점수 지표 (순수 함수) |
| `adapters/rendering.py` | Jinja2 기반 HTML 생성(반응형, 다크모드, 현지화, 크기 검사) |
| `adapters/aws.py` | S3, SES, SNS, SSM, Batch 연동 |

### 파이프라인

Expand All @@ -98,7 +103,7 @@ collect → gate → filter → rank → summarize → greet → render → deli
저장합니다.
- **SES**로 뉴스레터를 전달하고, **SNS**로는 문제가 있을 때만 알림을 보냅니다.
- **Bedrock(us-west-2)**. GPT-6.1 Sol(필터), Claude Sonnet 5.5(요약), Claude Haiku 5.5(인사말,
제목 Title Case, 출력 복구). [`app/src/constants.py`](./app/src/constants.py)의 `LanguageModelId`
제목 Title Case, 출력 복구). [`app/domain/constants.py`](./app/domain/constants.py)의 `LanguageModelId`
카탈로그에 있는 모델은 OpenAI GPT-6 모델을 포함해 스테이지별로 골라 쓸 수 있습니다.
모델별 요청 규칙(thinking 끄기 값, effort, GPT 호출 방식)은 design.md §8에 있습니다.

Expand Down Expand Up @@ -146,7 +151,7 @@ newsletter:
header_title: "Weekly AI Tech Blog Digest"
```

> 모델 ID는 [`app/src/constants.py`](./app/src/constants.py)의 `LanguageModelId`
> 모델 ID는 [`app/domain/constants.py`](./app/domain/constants.py)의 `LanguageModelId`
> 카탈로그에서 가져다 씁니다.

---
Expand Down Expand Up @@ -177,7 +182,7 @@ python scripts/put_inference_profiles.py --stage dev # 계정/스테이지마
- Billing → 비용 할당 태그에서 `Project` 태그를 활성화해야 Cost Explorer에 나타납니다(최대
24시간, 소급 적용 안 됨).

자세한 내용은 [design.md §8](./docs/design.md#8-bedrock-모델-팩토리-appsrcmodel_factorypy--배치-처리-appsrcutilspy)을 참고하세요.
자세한 내용은 [design.md §8](./docs/design.md#8-bedrock-모델-팩토리-appadaptersbedrockpy--배치-처리-appapplicationllm_batchingpy)을 참고하세요.

### 로컬 실행

Expand Down
27 changes: 17 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,22 @@ Powered by Amazon Bedrock (GPT-6.1 Sol filters, Claude summarizes) · orchestrat

### Core components

The code under `app/` has three layers: `domain/` holds the rules with no I/O,
`application/` the services written against the interfaces in
`application/ports.py`, and `adapters/` the code that talks to Bedrock, AWS, the
web and the templates. Dependencies point inward, `main.py` wires the layers
together, and `tests/test_architecture.py` checks the rule
([design.md §3](./docs/design.md#3-저장소-구조)).

| Module | Responsibility |
| --- | --- |
| `feed_parser.py` | RSS parsing + HTML scraping (BeautifulSoup4), per-source health tracking |
| `summarizer.py` | Content gate → relevance filter → rank/cap → summarize, all via Bedrock |
| `model_factory.py` | Bedrock model capability registry + LangChain chat-model construction, cost-attribution profiles |
| `quality_metrics.py` | Deterministic output-quality rubric (pure functions) |
| `eval_metrics.py` | Filtering-score metrics (pure functions) |
| `newsletter_renderer.py` | HTML generation with Jinja2 (responsive, dark-mode, localized, size-checked) |
| `aws_helpers.py` | S3, SES, SNS, SSM, and Batch operations |
| `adapters/feeds.py` | RSS parsing + HTML scraping (BeautifulSoup4), per-source health tracking |
| `application/summarize.py` | Content gate → relevance filter → rank/cap → summarize, all via Bedrock |
| `adapters/bedrock.py` | Bedrock model capability registry + LangChain chat-model construction, cost-attribution profiles |
| `domain/quality_metrics.py` | Deterministic output-quality rubric (pure functions) |
| `domain/eval_metrics.py` | Filtering-score metrics (pure functions) |
| `adapters/rendering.py` | HTML generation with Jinja2 (responsive, dark-mode, localized, size-checked) |
| `adapters/aws.py` | S3, SES, SNS, SSM, and Batch operations |

### Pipeline

Expand All @@ -101,7 +108,7 @@ collect → gate → filter → rank → summarize → greet → render → deli
- **SES**. newsletter delivery. **SNS** — alerts, sent only when something goes wrong.
- **Bedrock (us-west-2)**. GPT-6.1 Sol (filter), Claude Sonnet 5.5 (summarize), Claude
Haiku 5.5 (greeting, title casing and output repair). Any model in the `LanguageModelId`
catalog in [`app/src/constants.py`](./app/src/constants.py) is selectable per
catalog in [`app/domain/constants.py`](./app/domain/constants.py) is selectable per
stage, including OpenAI GPT-6 models; per-model request rules (thinking
off-switches, effort, the GPT dialect) are in design.md §8.

Expand Down Expand Up @@ -150,7 +157,7 @@ newsletter:
```

> Model IDs come from the `LanguageModelId` catalog in
> [`app/src/constants.py`](./app/src/constants.py).
> [`app/domain/constants.py`](./app/domain/constants.py).

---

Expand Down Expand Up @@ -182,7 +189,7 @@ profile falls back to the system-defined id. Each call also logs
- Activate the `Project` cost allocation tag under **Billing → Cost allocation tags**
(takes up to 24h, not retroactive).

See [design.md §8](./docs/design.md#8-bedrock-모델-팩토리-appsrcmodel_factorypy--배치-처리-appsrcutilspy) for details.
See [design.md §8](./docs/design.md#8-bedrock-모델-팩토리-appadaptersbedrockpy--배치-처리-appapplicationllm_batchingpy) for details.

### Run locally

Expand Down
7 changes: 3 additions & 4 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,9 @@
from app.configs import Config
from app.domain.constants import AppConstants, EnvVars, Language, LocalPaths, SSMParams
from app.logger import is_running_in_aws, logger
from app.paths import APP_DIR, REPO_ROOT

ROOT_DIR: Path = (
Path("/tmp") if is_running_in_aws() else Path(__file__).resolve().parent.parent
)
ROOT_DIR: Path = Path("/tmp") if is_running_in_aws() else REPO_ROOT


def _boto_sessions(config: Config) -> tuple[boto3.Session, boto3.Session]:
Expand Down Expand Up @@ -209,7 +208,7 @@ def _newsletter_builder(
return NewsletterBuilder(
inputs_dir,
outputs_dir,
Path(__file__).resolve().parent / LocalPaths.TEMPLATES_DIR.value,
APP_DIR / LocalPaths.TEMPLATES_DIR.value,
logos=config.newsletter.logos,
)

Expand Down
11 changes: 11 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,17 @@
두 이름으로 두 번 로드됐습니다. Lambda 이미지는 `app` 패키지 없이 `main.py`만 복사해 핸들러가
임포트 단계에서 실패할 수 있었습니다. Batch 이미지는 패키지를 `app/`에 두고
`python -m app.main`으로 실행합니다. Lambda 이미지는 같은 날 Lambda 경로와 함께 지웠습니다(인프라).
- **2026-11-01** `app/src`를 계층 셋으로 나눴습니다. 입출력이 없는 규칙은 `app/domain`, 포트에
대고 쓴 서비스는 `app/application`, Bedrock·AWS·HTTP·템플릿과 말하는 코드는 `app/adapters`로
옮겼고, `main.py`에 있던 파이프라인·발송·알림 함수도 `application`의 `pipeline.py`·
`delivery.py`·`alerts.py`로 나왔습니다. 그전에는 규칙 하나를 시험하려 해도 boto3·requests·
LangChain을 함께 임포트해야 했고, 발송과 알림 테스트는 `main`의 모듈 속성을 패치해 AWS 호출을
막았습니다. 서비스는 이제 `ChatModelProvider`·`ObjectStore`·`Mailer`·`AlarmPublisher` 같은
포트를 받고, `main.py`가 어댑터를 만들어 넘깁니다. 의존 방향은 `tests/test_architecture.py`가
임포트를 읽어 고정합니다. 동작은 바꾸지 않았고, 발송되는 호와 S3 키·원장을 고정한 스냅샷
테스트(`tests/test_golden_output.py`)가 단계마다 그대로 통과했습니다. `Post.from_entry`는 글
페이지를 받아 올 수 있어서 `feeds.post_from_entry`가 됐고, 로그 디렉터리는 `logger.py` 위치에서
상위 디렉터리를 세는 대신 `app/paths.py`의 `REPO_ROOT`에서 찾습니다.
- 베이스 이미지는 digest로 고정했지만 Python 의존성은 `>=` 범위뿐이어서 이미지 빌드마다 다시
해석됐습니다. `requirements.lock`을 도입했습니다. CI가 락을 constraints로 쓰기 전에는 핵심
패키지 12개가 이미지와 전부 달랐고 langchain은 1.2 대 1.3이었습니다.
Expand Down
Loading
Loading