diff --git a/README.md b/README.md index 9bdb698..a6eedad 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,8 @@ for the full architecture write-up. ## 🀝 κΈ°μ—¬ν•˜κΈ° +**처음 λ³΄μ‹œλŠ” 뢄은 [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)** β€” λ””λ ‰ν† λ¦¬Β·λ ˆμ΄μ–΄ μ±…μž„, ν•œ λ©”μ‹œμ§€μ˜ lifecycle, *μ–΄λ””λ₯Ό μˆ˜μ •ν•˜λ©΄ 쒋을지* κ°€ ν•œκ³³μ— 정리돼 μžˆμŠ΅λ‹ˆλ‹€. + ```bash git clone https://github.com/CausalInferenceLab/lang2sql.git cd lang2sql diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..b7fc58f --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,245 @@ +# Architecture β€” κΈ°μ—¬μžμš© ν•œλˆˆ κ°€μ΄λ“œ + +이 λ¬Έμ„œλŠ” *처음 λ³΄λŠ” μ‚¬λžŒλ„ 10λΆ„ μ•ˆμ— μ–΄λ”” 무엇이 μžˆλŠ”μ§€ / μ–΄λ””λ₯Ό μ†λŒ€λ©΄ 쒋은지* μ•Œ 수 μžˆλ„λ‘ μ“°μ—¬μ‘ŒμŠ΅λ‹ˆλ‹€. 상세 섀계 μ˜λ„λŠ” [`docs/discord_first_redesign_v4_1.md`](./discord_first_redesign_v4_1.md)에 μžˆμŠ΅λ‹ˆλ‹€. + +--- + +## 1. ν•œ λˆˆμ— λ³΄λŠ” μ•„ν‚€ν…μ²˜ + +``` + USER (Discord / CLI / ν–₯ν›„ SlackΒ·Web) + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ frontends/ ← μž…λ ₯ λ°›κ³  좜λ ₯ 보내기 (transport) β”‚ +β”‚ discord/ cli/ slack/(빈) web/(빈) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β–Ό (μΈν„°λž™μ…˜ β†’ Identity) +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ tenancy/ ContextConcierge ← *쑰립점* β”‚ +β”‚ μš”μ²­λ§ˆλ‹€ HarnessContextλ₯Ό ν•˜λ‚˜ λ§Œλ“€μ–΄ λ„˜κΉ€ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β–Ό (ctx = LLM+tools+session+...) +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ harness/ agent_loop β”‚ +β”‚ system prompt β†’ LLM β†’ tool 호좜 β†’ λ‹€μŒ ν„΄/μ’…λ£Œ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β–Ό (도ꡬ가 ctx의 포트λ₯Ό 호좜) +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ semantic/(β˜…β‘£)β”‚safety/(β˜…β‘ )β”‚memory/(β˜…β‘‘)β”‚ingest/(β˜…β‘’)β”‚ ← 4κΈ°λ‘₯ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό (λͺ¨λ‘ 포트(Protocol)둜 외뢀와 뢄리) +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ adapters/ μ™ΈλΆ€ μ‹œμŠ€ν…œκ³Όμ˜ λ§ˆμ§€λ§‰ ν•œ 쀄 β”‚ +β”‚ llm/openai_ Β· llm/fake β”‚ +β”‚ db/sqlalchemy_explorer Β· db/d1_explorer Β· db/postgres_explorer β”‚ +β”‚ storage/sqlite_store Β· storage/sqlite_semantic β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +핡심 원칙: **λ‘œμ§μ€ 포트(좔상)μ—λ§Œ 의쑴, μ–΄λŒ‘ν„°(ꡬ체)λŠ” κ°€μž₯μžλ¦¬μ—λ§Œ**. κ·Έλž˜μ„œ μƒˆ LLMΒ·μƒˆ DBΒ·μƒˆ frontendλ₯Ό *κΈ°μ‘΄ μ½”λ“œ μ•ˆ κ±΄λ“œλ¦¬κ³ * 끼울 수 μžˆμŠ΅λ‹ˆλ‹€. + +--- + +## 2. μ™œ 이런 ꡬ쑰? β€” 4κΈ°λ‘₯ (ν•΄κ²°ν•˜λ €λŠ” 문제) + +| β˜… | 이름 | ν’€λ €λŠ” ν˜„μ‹€ 문제 | 핡심 파일 | +|---|---|---|---| +| β‘  | **Safety pipeline** | SQL이 *μ‹€μˆ˜λ‘œ/μ•…μ˜λ‘œ* DBλ₯Ό λ§μΉ˜λŠ” 일 | [`src/lang2sql/safety/`](../src/lang2sql/safety/) | +| β‘‘ | **Memory 3μΆ•** | 봇이 μ–΄μ œ ν•œ μ–˜κΈ°Β·μ •μ˜λ₯Ό *κΈ°μ–΅ λͺ» 함* | [`src/lang2sql/memory/`](../src/lang2sql/memory/) | +| β‘’ | **Ingestion 맀트릭슀** | λΉ„μ¦ˆλ‹ˆμŠ€ μ •μ˜λ₯Ό *μ‚¬λžŒμ΄ 일일이* μž…λ ₯ν•΄μ•Ό 함 | [`src/lang2sql/ingestion/`](../src/lang2sql/ingestion/) | +| β‘£ | **Semantic federation** | 같은 *"ν™œμ„± μ‚¬μš©μž"* κ°€ νŒ€λ§ˆλ‹€ 의미 닀름 | [`src/lang2sql/semantic/`](../src/lang2sql/semantic/) | + +μžμ„Έν•œ 배경은 redesign λ¬Έμ„œ Β§3을 μ°Έκ³ . + +--- + +## 3. λ””λ ‰ν† λ¦¬Β·λ ˆμ΄μ–΄ κ°€μ΄λ“œ + +> 의쑴 λ°©ν–₯: `frontends β†’ tenancy β†’ harness β†’ semantic/safety/memory/ingestion/tools β†’ core ← adapters` +> `core/`λŠ” λˆ„κ΅¬λ„ μ˜μ‘΄ν•˜μ§€ μ•ŠλŠ” *순수* μ˜μ—­(νƒ€μž…+포트). μƒˆ λͺ¨λ“ˆ μΆ”κ°€ μ‹œ 이 λ°©ν–₯을 κΉ¨μ§€ μ•Šκ²Œ. + +### `src/lang2sql/core/` β€” 순수 νƒ€μž… + 포트 (β˜… μ†λŒ€μ§€ λ§ˆμ„Έμš”) +μ‹œμŠ€ν…œ μ „μ²΄μ˜ *μ–΄νœ˜*κ°€ λͺ¨μ—¬ μžˆμŠ΅λ‹ˆλ‹€. μ™ΈλΆ€ 의쑴 0, I/O 0. +- [`types.py`](../src/lang2sql/core/types.py) β€” `Message`, `ToolCall`, `ToolResult`, `Completion`, `Role` +- [`identity.py`](../src/lang2sql/core/identity.py) β€” `Identity`, `Scope`, federation의 `scope_chain()` μˆœμ„œ (narrowβ†’wide) +- [`ports/`](../src/lang2sql/core/ports/) β€” 11개 Protocol: `LLMPort`, `ExplorerPort`, `ToolPort`, `SafetyLayerPort`, `SafetyPipelinePort`, `StorePort`, `RecallPort`, `ExtractorPort` (memory), `SourcePort`, `DocExtractorPort`, `ScopeResolverPort`, `FrontendPort`, `SecretsPort`, `SessionStorePort`, `AuditPort` + +### `src/lang2sql/harness/` β€” μ—μ΄μ „νŠΈ ν•œ ν„΄μ˜ μ—”μ§„ +- [`context.py`](../src/lang2sql/harness/context.py) β€” `HarnessContext` (llm + tools + safety + explorer + scope_resolver + session ν•œ λ‹€λ°œ) +- [`session.py`](../src/lang2sql/harness/session.py) β€” λŒ€ν™” transcript +- [`loop.py`](../src/lang2sql/harness/loop.py) β€” `agent_loop`: system prompt β†’ LLM β†’ tool 호좜 β†’ λ‹€μŒ ν„΄ +- [`tool_registry.py`](../src/lang2sql/harness/tool_registry.py) β€” 이름→도ꡬ dispatch +- [`system_prompt.py`](../src/lang2sql/harness/system_prompt.py) β€” μ‹œλ©˜ν‹± + μŠ€ν‚€λ§ˆ μ£Όμž… + +### `src/lang2sql/semantic/` β€” μ‹œλ©˜ν‹± λ ˆμ΄μ–΄ + federation (β˜…β‘£) +- [`types.py`](../src/lang2sql/semantic/types.py) β€” `SemanticEntry` (METRIC/DIMENSION/RELATIONSHIP/RULE) +- [`layer.py`](../src/lang2sql/semantic/layer.py) β€” `SemanticLayer.render()` (μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈλ‘œ 듀어감) +- [`scoped_layer.py`](../src/lang2sql/semantic/scoped_layer.py) β€” *κ°€μž₯ ꡬ체적 scopeκ°€ 승리*ν•˜λŠ” merge +- [`store.py`](../src/lang2sql/semantic/store.py) β€” in-memory store +- [`sql_composer.py`](../src/lang2sql/semantic/sql_composer.py) β€” metric 이름 β†’ μ •μ˜ 펼치기 (V1 μ΅œμ†Œ) + +### `src/lang2sql/safety/` β€” Read-only 게이트 (β˜…β‘ ) +- [`pipeline.py`](../src/lang2sql/safety/pipeline.py) β€” layerλ₯Ό μˆœμ„œλŒ€λ‘œ 톡과, *첫 λΉ„-PASSμ—μ„œ 차단* +- [`layers/whitelist.py`](../src/lang2sql/safety/layers/whitelist.py) β€” SELECT/WITH만 톡과, DML ν‚€μ›Œλ“œ fail-closed +- [`layers/timeout.py`](../src/lang2sql/safety/layers/timeout.py) β€” μ‹€ν–‰ timeout config +- [`tests/test_safety.py`](../tests/test_safety.py) β€” **12개 νšŒκ·€ μΌ€μ΄μŠ€** (λ¨Έμ§€ 게이트) + +### `src/lang2sql/memory/` β€” Hermes 3μΆ• (β˜…β‘‘) +- [`stores/in_memory.py`](../src/lang2sql/memory/stores/in_memory.py) β€” Where +- [`recall/inject_all.py`](../src/lang2sql/memory/recall/inject_all.py) β€” What +- [`extractors/manual.py`](../src/lang2sql/memory/extractors/manual.py) β€” How new +- [`service.py`](../src/lang2sql/memory/service.py) β€” 셋을 묢음 + +### `src/lang2sql/ingestion/` β€” λ¬Έμ„œ β†’ μ‹œλ©˜ν‹± 후보 (β˜…β‘’) +- [`sources/file_source.py`](../src/lang2sql/ingestion/sources/file_source.py) β€” μ–΄λ””μ„œ +- [`extractors/llm_extractor.py`](../src/lang2sql/ingestion/extractors/llm_extractor.py) β€” μ–΄λ–»κ²Œ μΆ”μΆœ +- [`pipeline.py`](../src/lang2sql/ingestion/pipeline.py) β€” Source Γ— Extractor matrix + +### `src/lang2sql/tools/` β€” μ—μ΄μ „νŠΈκ°€ λΆ€λ₯΄λŠ” capability +6개 도ꡬ (λͺ¨λ‘ ctx-aware, async): +- [`run_sql.py`](../src/lang2sql/tools/run_sql.py) β€” safety 톡과 ν›„ explorer둜 μ‹€ν–‰ +- [`explore_schema.py`](../src/lang2sql/tools/explore_schema.py) β€” ν…Œμ΄λΈ”/컬럼 introspection +- [`define_metric.py`](../src/lang2sql/tools/define_metric.py) β€” scope-aware μ •μ˜ μ“°κΈ° +- [`remember.py`](../src/lang2sql/tools/remember.py) β€” fact μ €μž₯ +- [`ask_user.py`](../src/lang2sql/tools/ask_user.py) β€” λͺ¨ν˜Έν•˜λ©΄ μ‚¬μš©μžμ—κ²Œ 질문 +- [`ingest_doc.py`](../src/lang2sql/tools/ingest_doc.py) β€” λ¬Έμ„œ β†’ 후보 μ œμ•ˆ +- [`__init__.py: build_default_tools`](../src/lang2sql/tools/__init__.py) β€” μ–΄μ…ˆλΈ”λ¦¬ + +### `src/lang2sql/tenancy/` β€” 쑰립점 +- [`concierge.py`](../src/lang2sql/tenancy/concierge.py) β€” *μœ μΌν•˜κ²Œ* ꡬ체 클래슀λ₯Ό import ν•˜λŠ” κ³³. μš”μ²­λ§ˆλ‹€ `HarnessContext` λ§Œλ“¦. +- [`scope_resolver.py`](../src/lang2sql/tenancy/scope_resolver.py) β€” `ScopeResolverPort` κ΅¬ν˜„ (semantic μœ„) +- [`encrypted_secrets.py`](../src/lang2sql/tenancy/encrypted_secrets.py) β€” `cryptography.Fernet` μ‹€ μ•”ν˜Έν™” + +### `src/lang2sql/adapters/` β€” μ™ΈλΆ€ μ‹œμŠ€ν…œκ³Όμ˜ λ§ˆμ§€λ§‰ 쀄 +- `llm/openai_.py` β€” urllib 기반 OpenAI tool-calling +- `llm/fake.py` β€” μ˜€ν”„λΌμΈ ν…ŒμŠ€νŠΈμš© 결정적 LLM +- `db/sqlalchemy_explorer.py` β€” **DSN만 λ°”κΎΈλ©΄ Postgres/MySQL/Snowflake/BigQuery/DuckDB λ‹€ 컀버** +- `db/d1_explorer.py` β€” Cloudflare D1 (HTTP API, urllib) +- `db/factory.py` β€” `build_explorer(connection)` scheme λΌμš°νŒ… +- `db/postgres_explorer.py` β€” V1 stub (psycopg λ―Έμ„€μΉ˜ ν™˜κ²½μš©) +- `storage/sqlite_store.py` β€” `AuditPort` + `SessionStorePort` + kv +- `storage/sqlite_semantic.py` β€” μ‹œλ©˜ν‹± μ •μ˜ μ˜μ†ν™” + +### `src/lang2sql/frontends/` β€” μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€ +- [`discord/bot.py`](../src/lang2sql/frontends/discord/bot.py) β€” **μœ μΌν•˜κ²Œ** `discord.py`λ₯Ό import +- [`discord/commands.py`](../src/lang2sql/frontends/discord/commands.py) β€” 순수 ν•Έλ“€λŸ¬ (discord λΉ„μ˜μ‘΄, ν…ŒμŠ€νŠΈ κ°€λŠ₯) +- [`discord/setup_wizard.py`](../src/lang2sql/frontends/discord/setup_wizard.py) β€” `/setup` Modal/Select +- [`discord/session_router.py`](../src/lang2sql/frontends/discord/session_router.py) β€” discord ID β†’ `Identity` +- [`discord/render.py`](../src/lang2sql/frontends/discord/render.py) β€” >50행이면 CSV 첨뢀 +- [`cli/app.py`](../src/lang2sql/frontends/cli/app.py) β€” 개발용 CLI + +--- + +## 4. ν•œ λ©”μ‹œμ§€μ˜ lifecycle (λ””μŠ€μ½”λ“œ λ©˜μ…˜ ν•œ 번 따라가기) + +``` +1. μ‚¬μš©μž: "@lang2sql-test 이번 달 맀좜 μ•Œλ €μ€˜" +2. discord/bot.py: on_message β†’ _message_context()둜 (guild_id, channel_id, user_id) λ½‘μŒ +3. session_router.to_identity() β†’ Identity(...) +4. CommandHandlers.query(identity, "이번 달 맀좜 μ•Œλ €μ€˜") +5. ContextConcierge.build_context(identity) + - secretsμ—μ„œ κΈΈλ“œλ³„ db_dsn μžˆλ‚˜? β†’ 있으면 build_explorer둜 κ·Έ DB μ‚¬μš© (μΊμ‹œ) + - SqliteStoreμ—μ„œ μ„Έμ…˜ λ‘œλ“œ (μ—†μœΌλ©΄ μƒˆλ‘œ) + - build_default_tools()둜 ToolRegistry 채움 + - HarnessContext λ°˜ν™˜ +6. agent_loop(ctx, "이번 달 맀좜 μ•Œλ €μ€˜") + - system_prompt: μ‹œλ©˜ν‹± effective_layer + μŠ€ν‚€λ§ˆ μ£Όμž… + - LLM(GPT-4.1-mini): "run_sql 도ꡬλ₯Ό λΆ€λ₯΄μ„Έμš”" 응닡 + - tools.dispatch("run_sql", {sql: "SELECT ..."}, ctx) + β†’ safety.evaluate(sql) β†’ PASS + β†’ explorer.execute(sql) β†’ ν–‰λ“€ λ°˜ν™˜ + - κ²°κ³Ό messages에 μΆ”κ°€, LLM λ‹€μ‹œ 호좜 β†’ μ΅œμ’… λ‹΅λ³€ +7. concierge.store.save(session_key, ctx.session) ← μ„Έμ…˜ μ˜μ†ν™” +8. render_answer(answer) β†’ OutboundMessage +9. interaction.followup.send(...) β†’ Discord에 λ‹΅ +``` + +--- + +## 5. μ–΄λ””λ₯Ό μˆ˜μ •ν•˜λ©΄ μ’‹μ„κΉŒ β€” Extension Points + +κΈ°μ—¬ PR을 λ°›κΈ° κ°€μž₯ μ‰¬μš΄ 지점듀. μ „λΆ€ *κΈ°μ‘΄ μ½”λ“œ μ•ˆ κ±΄λ“œλ¦¬κ³  μΆ”κ°€λ§Œ ν•˜λ©΄ λ©λ‹ˆλ‹€*. + +### LLM μΆ”κ°€ (예: Anthropic Claude, NIM) +1. `src/lang2sql/adapters/llm/_.py` μƒˆλ‘œ μž‘μ„±, `LLMPort` κ΅¬ν˜„ +2. `tenancy/concierge.py: _default_llm()`에 λΆ„κΈ° μΆ”κ°€ +3. tests/ 에 `test__adapter.py` + +### μƒˆ DB 지원 +SQLAlchemy 지원 DB라면: +1. `pyproject.toml`의 `[project.optional-dependencies]`에 extra μΆ”κ°€ +2. 끝. `SqlAlchemyExplorer`κ°€ DSN으둜 μ•Œμ•„μ„œ 처리 + +SQLAlchemy 미지원 (예: 자체 HTTP API): +1. `adapters/db/_explorer.py`에 `ExplorerPort` κ΅¬ν˜„ +2. `adapters/db/factory.py`의 `build_explorer`에 scheme λΆ„κΈ° +3. `adapters/db/dsn_builder.py`에 `build_()` + `FIELD_SCHEMA[]` +4. tests/ + +### μƒˆ safety layer (예: AST μ •λ°€ 검증, ν•¨μˆ˜ 차단, EXPLAIN λΉ„μš©) +1. `safety/layers/.py`에 `SafetyLayerPort` κ΅¬ν˜„ +2. `safety/pipeline.py`의 `SafetyPipeline` κΈ°λ³Έ layers λͺ©λ‘μ— λΌμš°κ±°λ‚˜, μ˜΅μ…”λ„λ‘œ λ…ΈμΆœ +3. tests/test_safety.py에 νšŒκ·€ μΌ€μ΄μŠ€ μΆ”κ°€ + +### 더 λ˜‘λ˜‘ν•œ memory recall (예: ν‚€μ›Œλ“œ, 벑터) +1. `memory/recall/.py`에 `RecallPort` κ΅¬ν˜„ +2. conciergeμ—μ„œ μ˜΅μ…˜μœΌλ‘œ 선택 κ°€λŠ₯ν•˜κ²Œ +3. tests/ + +### μƒˆ ingestion source (예: URL, Notion MCP) +1. `ingestion/sources/.py`에 `SourcePort` κ΅¬ν˜„ +2. ingestion 도ꡬ 흐름이 μžλ™ λ§€νŠΈλ¦­μŠ€μ΄λ―€λ‘œ μΆ”κ°€ μ½”λ“œ 거의 μ—†μŒ + +### μƒˆ frontend (예: Slack, Web) +1. `frontends//` 디렉토리에 transport μž‘μ„± +2. `commands.py`λŠ” κ·ΈλŒ€λ‘œ μž¬μ‚¬μš© (discord λΉ„μ˜μ‘΄μ΄λΌ) +3. `core/ports/frontend.py`의 `FrontendPort` μΈν„°νŽ˜μ΄μŠ€ λ”°λ₯΄κΈ° + +### μƒˆ 도ꡬ (예: visualize, write_code) +1. `tools/.py`에 `ToolPort` κ΅¬ν˜„ (spec + run) +2. `tools/__init__.py: build_default_tools()`에 μΆ”κ°€ +3. tests/ + +--- + +## 6. λΉ λ₯Έ κΈ°μ—¬ μ‹œμž‘ (5λΆ„) + +```bash +git clone https://github.com/CausalInferenceLab/Lang2SQL.git +cd Lang2SQL +uv sync # κΈ°λ³Έ deps +.venv/bin/pytest -q # 106 ν…ŒμŠ€νŠΈ 톡과 확인 +.venv/bin/python bench/ecommerce_demo.py # federation + safety 둜컬 데λͺ¨ +``` + +브랜치 β†’ μ½”λ“œ + ν…ŒμŠ€νŠΈ β†’ PR. CIλŠ” λ”°λ‘œ μ—†μœΌλ‹ˆ *λ‘œμ»¬μ—μ„œ pytest 확인 ν›„ PR*. + +--- + +## 7. μ½”λ“œ μ»¨λ²€μ…˜ (μž‘μ€ 약속) + +| κ·œμΉ™ | 이유 | +|---|---| +| **ν¬νŠΈλŠ” `typing.Protocol`** (`runtime_checkable` ꢌμž₯) | 덕타이핑 + isinstance κ°€λŠ₯ | +| **μ–΄λŒ‘ν„°μ˜ engine/connection은 lazy** | λΌμš°νŒ… λ‹¨κ³„μ—μ„œ λ“œλΌμ΄λ²„ λ―Έμ„€μΉ˜μ—¬λ„ OK | +| **blocking ν˜ΈμΆœμ€ `asyncio.to_thread`** | discord 이벀트 루프 막지 μ•ŠκΈ° | +| **frontends/discordμ—μ„œ `discord.py` importλŠ” `bot.py`Β·`setup_wizard.py`만** | λ‘œμ§μΈ΅μ€ μœ λ‹›ν…ŒμŠ€νŠΈ κ°€λŠ₯ν•΄μ•Ό 함 | +| **μƒˆ ν™˜κ²½λ³€μˆ˜λŠ” `.env.example`에도 λ¬Έμ„œν™”** | μ‹ κ·œ μ»¨νŠΈλ¦¬λ·°ν„° μΉœν™” | +| **ν…ŒμŠ€νŠΈλŠ” 토큰/λ„€νŠΈμ›Œν¬ 없이도 톡과해야 함** | `FakeLLM` / mock transport ν™œμš© | +| **포트(`core/ports/`)λŠ” 거의 frozen** | 변경은 λͺ¨λ“  μ–΄λŒ‘ν„°/κ΅¬ν˜„μ— 영ν–₯ β€” 정말 ν•„μš”ν•œμ§€ ν•œ 번 더 κ³ λ―Ό | + +--- + +## 8. 더 깊이 보고 μ‹Άλ‹€λ©΄ + +- [`docs/discord_first_redesign_v4_1.md`](./discord_first_redesign_v4_1.md) β€” *μ™œ μ΄λ ‡κ²Œ λ§Œλ“€μ—ˆλ‚˜* (μž₯λ¬Έ) +- [`docs/discord_first_redesign_v4_2.md`](./discord_first_redesign_v4_2.md) β€” ν™•μ • 컨셉 μš”μ•½ (단문) +- [`docs/DEPLOY.md`](./DEPLOY.md) β€” Discord 봇 운영 +- [`bench/ecommerce_demo.py`](../bench/ecommerce_demo.py) β€” federation/safety 라이브 데λͺ¨ +- ν…ŒμŠ€νŠΈκ°€ 사싀상 μ‚¬μ–‘μ„œ β€” `tests/test_*.py`λ₯Ό *λͺ¨λ“ˆλ³„ κ°€μ΄λ“œ*둜 ν™œμš© + +--- + +질문/μ œμ•ˆμ€ [Discord](https://discord.gg/EPurkHVtp2) λ˜λŠ” GitHub Issues ν™˜μ˜.