Skip to content

Commit de05928

Browse files
authored
Merge pull request #248 from thrcle/feat/layer-rename
refactor: rename semantic layers (guild→org, channel→team, member→user)
2 parents adf1d9e + 551ee19 commit de05928

21 files changed

Lines changed: 179 additions & 181 deletions

docs/ARCHITECTURE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
- [`run_sql.py`](../src/lang2sql/tools/run_sql.py) — safety 통과 후 explorer로 실행
100100
- [`explore_schema.py`](../src/lang2sql/tools/explore_schema.py) — 테이블/컬럼 introspection
101101
- [`enrich_schema.py`](../src/lang2sql/tools/enrich_schema.py) — LLM으로 컬럼 메타데이터 자동 보강
102-
- [`semantic_federation.py`](../src/lang2sql/tools/semantic_federation.py)`term_custom`: guild/channel/member 계층 용어 사전 (KV 기반, narrow→wide lookup)
102+
- [`semantic_federation.py`](../src/lang2sql/tools/semantic_federation.py)`term_custom`: org/team/user 계층 용어 사전 (KV 기반, narrow→wide lookup)
103103
- [`org_setup.py`](../src/lang2sql/tools/org_setup.py) — 전사/팀 단위 용어 일괄 등록
104104
- [`remember.py`](../src/lang2sql/tools/remember.py) — fact 저장
105105
- [`ask_user.py`](../src/lang2sql/tools/ask_user.py) — 모호하면 사용자에게 질문

docs/USAGE.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ DB가 연결되지 않으면 SQL을 실행할 수 없습니다.
2626

2727
DB 종류(PostgreSQL, MySQL, SQLite 등)를 선택하는 안내가 나타납니다. 접속 정보를 입력하면 연결됩니다. DSN을 직접 알고 있다면 `/connect dsn:...`으로 바로 입력할 수도 있습니다.
2828

29-
> 관리자 권한이 필요합니다.
3029

3130
---
3231

@@ -87,7 +86,7 @@ DB 스키마를 분석해 비즈니스 용어 후보를 자동으로 뽑습니
8786
같은 용어가 여러 레이어에 등록된 경우 **좁은 범위가 우선** 적용됩니다.
8887

8988
```
90-
개인(member) > 채널(channel) > 전사(guild)
89+
개인(member) > 채널(channel) > 전사(org)
9190
```
9291

9392
예를 들어 "활성고객"을 전사에서는 "30일 내 로그인"으로 정의했더라도, 마케팅 채널에서 "14일 내 로그인"으로 따로 등록하면 마케팅 채널 안에서만 그 정의가 우선 적용됩니다. 다른 채널에는 영향이 없습니다.
@@ -98,10 +97,9 @@ DB 스키마를 분석해 비즈니스 용어 후보를 자동으로 뽑습니
9897

9998
| 커맨드 | 설명 |
10099
|---|---|
101-
| `/setup` | DB 연결 마법사 (관리자) |
100+
| `/setup` | DB 연결 마법사 |
102101
| `/connect dsn:...` | DSN으로 직접 DB 연결 |
103102
| `/ingest content:...` | 텍스트에서 용어 후보 추출 |
104-
| `/ingest ref:파일명` | 서버 파일에서 용어 후보 추출 |
105103
| `/confirm_ingest ref:... accept:... layer:...` | 추출된 후보 검토 후 등록 |
106104
| `/term_custom` | 용어 직접 등록 (위저드) |
107105
| `/term_custom action:show` | 등록된 용어 전체 조회 |
@@ -111,6 +109,7 @@ DB 스키마를 분석해 비즈니스 용어 후보를 자동으로 뽑습니
111109
| `/enrich` | DB 컬럼 메타데이터 자동 보강 |
112110
| `/remember text:...` | 사실 기억 저장 |
113111
| `/audit_me` | 내 활동 이력 조회 |
112+
| `/help` | 사용 방법 안내 |
114113

115114
---
116115

@@ -119,8 +118,8 @@ DB 스키마를 분석해 비즈니스 용어 후보를 자동으로 뽑습니
119118
**Q. 질문했는데 엉뚱한 SQL이 나와요.**
120119
등록된 용어가 없거나 DB 메타데이터가 부족한 경우입니다. `/enrich`로 컬럼 설명을 보강하거나 `/term_custom`으로 관련 용어를 등록해보세요.
121120

122-
**Q. "guild 용어는 관리자만 등록 가능" 오류가 나요.**
123-
`layer:guild`는 관리자 권한이 필요합니다. `layer:channel` 채널 범위로 등록하거나 관리자에게 요청하세요.
121+
**Q. "org 용어는 관리자만 등록 가능" 오류가 나요.**
122+
`layer:org`는 관리자 권한이 필요합니다. `layer:team`으로 채널 범위로 등록하거나 관리자에게 요청하세요.
124123

125124
**Q. 이전 대화 내용을 기억하나요?**
126125
같은 채널(또는 DM 스레드)에서 이어지는 대화는 맥락이 유지됩니다. `/remember`로 중요한 사실을 명시적으로 저장할 수도 있습니다.

src/lang2sql/adapters/storage/okf_bundle.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def import_(self, store: "SqliteStore", kv_scope: str) -> int:
105105
# ------------------------------------------------------------------
106106

107107
def _scope_dir(self, entry: FedEntry) -> Path:
108-
label = "guild" if entry.layer == "guild" else f"{entry.layer}:{entry.entity}"
108+
label = "org" if entry.layer == "org" else f"{entry.layer}:{entry.entity}"
109109
return self.base_dir / label
110110

111111
def _concept_path(self, entry: FedEntry) -> Path:
@@ -172,7 +172,7 @@ def _md_to_entry(path: Path) -> FedEntry | None:
172172

173173
return FedEntry(
174174
term=str(fm.get("title", path.stem)),
175-
layer=str(fm.get("layer", "guild")),
175+
layer=str(fm.get("layer", "org")),
176176
entity=str(fm.get("entity", "")),
177177
definition=str(fm.get("description", "")),
178178
synonyms=fm.get("synonyms") or [],

src/lang2sql/core/identity.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ class ScopeLevel(str, Enum):
2121
"""
2222

2323
THREAD = "thread"
24-
CHANNEL = "channel"
25-
GUILD = "guild"
24+
CHANNEL = "team"
25+
GUILD = "org"
2626
BUILTIN = "builtin"
2727

2828

src/lang2sql/frontends/discord/bot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ async def confirm_ingest(
176176
interaction: discord.Interaction,
177177
ref: str,
178178
accept: str = "all",
179-
layer: str = "channel",
179+
layer: str = "team",
180180
) -> None:
181181
await self._run(
182182
interaction,
@@ -219,7 +219,7 @@ async def term_custom(
219219
interaction: discord.Interaction,
220220
action: str = "",
221221
term: str = "",
222-
layer: str = "member",
222+
layer: str = "team",
223223
) -> None:
224224
ident = to_identity(_interaction_context(interaction))
225225
if action == "show":

src/lang2sql/frontends/discord/commands.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ async def term_custom(
179179
identity: Identity,
180180
term: str = "",
181181
definition: str = "",
182-
layer: str = "member",
182+
layer: str = "team",
183183
synonyms: str = "",
184184
inferred: bool = False,
185185
scan: bool = False,
@@ -252,7 +252,7 @@ async def confirm_ingest(
252252
identity: Identity,
253253
ref: str,
254254
accept: str = "all",
255-
layer: str = "channel",
255+
layer: str = "team",
256256
) -> OutboundMessage:
257257
"""ingest_doc로 추출한 후보를 검토 후 시멘틱 레이어에 등록."""
258258
ctx = await self._concierge.build_context(identity)
@@ -270,24 +270,24 @@ async def help(self) -> OutboundMessage:
270270
**Lang2SQL 사용 가이드**
271271
272272
**📊 질문하기**
273-
봇을 멘션하거나 채널에서 자연어로 질문하세요.
273+
봇을 멘션하거나 스레드에서 자연어로 질문하세요.
274274
> @Lang2SQL 이번 달 매출 상위 고객 10명 알려줘
275275
276-
**🗄️ DB 연결** (관리자)
276+
**🗄️ DB 연결**
277277
`/setup` — 안내에 따라 DB 접속 정보 입력
278278
`/connect dsn:...` — DSN 직접 입력
279279
280280
**📖 비즈니스 용어 등록**
281281
`/ingest content:월매출은 SUM(orders.amount), 활성고객은 30일 내 로그인`
282282
→ 후보 추출 후 아래 커맨드로 확정
283-
`/confirm_ingest ref:inline:xxxx accept:all layer:channel`
283+
`/confirm_ingest ref:inline:xxxx accept:all layer:team`
284284
285285
`/term_custom` — 용어 직접 등록 (위저드)
286286
`/term_custom action:show` — 등록된 용어 조회
287287
`/org_setup org:회사명` — DB 스캔으로 용어 자동 추출
288288
289289
**🏷️ 용어 우선순위**
290-
개인(member) > 채널(channel) > 전사(guild)
290+
개인(user) > 팀(team) > 전사(org)
291291
같은 채널 안에서 등록한 정의가 전사 정의보다 우선 적용됩니다.
292292
293293
**🔧 기타**

src/lang2sql/frontends/discord/term_wizard.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""term_wizard.py — /term_custom 등록 폼 (2단계 UI).
22
3-
Step 1: Select — 전사(guild) / 채널·팀(channel) / 개인(member) 선택
3+
Step 1: Select — 전사(org) / 채널·팀(channel) / 개인(member) 선택
44
Step 2: Modal — 용어명·정의·동의어 입력
55
66
채널이 팀 경계 역할을 하므로 entity 직접 입력 불필요.
@@ -23,17 +23,17 @@
2323
_LAYER_OPTIONS = [
2424
discord.SelectOption(
2525
label="전사 (Guild) — 회사 공통 정의",
26-
value="guild",
26+
value="org",
2727
description="모든 채널에서 기본값으로 사용",
2828
),
2929
discord.SelectOption(
3030
label="채널 (팀) — 이 채널 전용 정의",
31-
value="channel",
31+
value="team",
3232
description="다른 채널과 충돌 없이 이 채널에서만 유효",
3333
),
3434
discord.SelectOption(
3535
label="개인 — 나만 사용하는 정의",
36-
value="member",
36+
value="user",
3737
description="전사·채널 정의를 조용히 덮어씀",
3838
),
3939
]

src/lang2sql/harness/system_prompt.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
- Discover schema with explore_schema before guessing table or column names.
2222
- Prefer definitions from the semantic layer below over your own assumptions.
2323
- Answer concisely. Show only the final successful SQL you ran, not intermediate attempts.
24+
25+
Clarification rule:
26+
- If the user's query contains business terms (e.g. '활성고객', '월매출', '신규유저') that are NOT defined in the semantic layer and whose meaning is ambiguous, ask ONE concise clarifying question BEFORE running any SQL. Do not guess.
27+
- After answering with SQL results, if you had to infer a term's meaning yourself, suggest the user save it: "이 정의를 저장하려면 `/term_custom`으로 등록해두세요. 다음 질문부터 자동 적용됩니다."
2428
"""
2529

2630

src/lang2sql/tenancy/concierge.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,13 @@ def _default_llm() -> LLMPort:
150150
model = os.environ.get("LANG2SQL_LLM_MODEL", "default")
151151
# Local servers (vLLM, Ollama) speak OpenAI-compatible API; dummy key satisfies the header.
152152
api_key = os.environ.get("OPENAI_API_KEY") or "local"
153+
timeout = float(os.environ.get("LANG2SQL_LLM_TIMEOUT", "120"))
153154
url = base_url.rstrip("/")
154155
if not url.endswith("/chat/completions"):
155156
if not url.endswith("/v1"):
156157
url = url + "/v1"
157158
url = url + "/chat/completions"
158-
return OpenAILLM(model=model, api_key=api_key, base_url=url)
159+
return OpenAILLM(model=model, api_key=api_key, base_url=url, timeout=timeout)
159160
if os.environ.get("OPENAI_API_KEY"):
160161
return OpenAILLM()
161162
return FakeLLM()

src/lang2sql/tools/confirm_ingest.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ def spec(self) -> ToolSpec:
5858
},
5959
"layer": {
6060
"type": "string",
61-
"enum": ["guild", "channel", "member"],
61+
"enum": ["org", "team", "user"],
6262
"description": "scope to register under (default: channel)",
63-
"default": "channel",
63+
"default": "team",
6464
},
6565
},
6666
"required": ["ref"],
@@ -70,7 +70,7 @@ def spec(self) -> ToolSpec:
7070
async def run(self, args: dict[str, Any], ctx: "HarnessContext") -> ToolResult:
7171
ref = (args.get("ref") or "").strip()
7272
accept = (args.get("accept") or "all").strip()
73-
layer_raw = (args.get("layer") or "channel").strip()
73+
layer_raw = (args.get("layer") or "team").strip()
7474

7575
if not ref:
7676
return ToolResult(call_id="", content="'ref' is required.", is_error=True)
@@ -113,8 +113,8 @@ async def run(self, args: dict[str, Any], ctx: "HarnessContext") -> ToolResult:
113113

114114
entity = (
115115
""
116-
if layer == "guild"
117-
else (channel_id if layer == "channel" else ctx.identity.user_id)
116+
if layer == "org"
117+
else (channel_id if layer == "team" else ctx.identity.user_id)
118118
)
119119
registered: list[str] = []
120120
for cand in selected:

0 commit comments

Comments
 (0)