---
title: "점검보고서 — 달록 코칭AI LLM/API 스위칭 가능성 (provider 종속성 감사)"
category: "report"
parent: "기술 점검 리서치"
document_type: "리서치보고서"
source_status: "original"
knowledge_group: "04_research"
priority: "High"
purpose: "달록(PaceLog) 코칭AI(코치노트 AI RC2 chat-proxy)와 AI 브리프(brief-proxy)가 특정 LLM/업체에 영구 종속되는 구조인지, OpenAI·Anthropic·Gemini·xAI·자체호스팅으로 교체 가능한 구조인지 읽기전용 점검한 보고서. 모델 호출부 분리, provider/model 설정·DB컬럼, 프롬프트·가드레일 위치, 응답 파싱 종속성, 메모리/기록 로딩 구조, 사용자 데이터 주권, 자체호스팅 전환 가능성, 종속지점 목록, P0/P1/P2 개선제안을 포함. 코드 무수정·KB 문서 근거 기반."
read_when: ["AI브리프·코칭","DB·RLS·보안","제품로드맵","검수·QA"]
updated: "2026-06-07"
work_timestamp: "20260607_120000"
context: "달록툴레포CC (dallog-tools, KB 빌더) — 외부 온라인앱 디스패치. 본레포(D:\\dallog\\dallog_git) 코드는 이 샌드박스에 없어 직접 열람 불가 → 달록KB 문서(RC2 spec 작업보고·핸드오프·총괄검수·브리프 전환 보고)를 근거로 한 읽기전용 점검."
source_of_truth: "https://dallog-tools.hansbridge.co.kr/knowledge/"
---

# 점검보고서 — 달록 코칭AI LLM/API 스위칭 가능성

> **점검 성격**: 코드 무수정 읽기전용 감사. 달록 서비스 코드는 본레포(`D:\dallog\dallog_git`)에 있고 본 점검 환경(dallog-tools, KB 빌더)에는 없다. 따라서 본 보고서는 **달록KB에 보존된 1차 기록**(RC2 코치노트 AI spec01~06 작업보고·핸드오프, RC2 Phase0 총괄검수, AI 브리프 OpenAI 전환 작업보고 등)을 근거로 작성했다. KB 총괄검수 문서가 코드를 `file:line`까지 대조한 기록이므로 신뢰도는 높으나, **최종 확정은 본레포 실코드 대조를 권고**한다.

---

## 1. 결론 요약

- **한 문장 판정**: 달록 코칭AI는 특정 업체에 영구 종속된 구조가 **아니다**. provider/model을 **환경변수로 교체**하도록 처음부터 설계됐고(중계 워커가 Gemini/OpenAI/Anthropic 3社를 지원, 모델 하드코딩 금지), 코칭 로직·메모리·원천 데이터는 **모델과 분리된 달록 자체 자산**이라 판단 엔진(LLM)만 갈아끼울 수 있는 구조다.
- **판정 등급: A (provider 교체가 비교적 쉬움)**
  - 단, 두 가지 단서를 명시한다.
    - **A-단서①**: 본 점검은 KB 문서 기반이다(코드 직접 열람 아님) → 본레포 실코드 최종 확인 권고.
    - **A-단서②**: 이미 코딩된 3社(Gemini·OpenAI·Anthropic) 밖의 **신규 provider(xAI Grok 등)나 자체호스팅**으로 갈 때는 중계 워커에 어댑터 1개를 추가하거나 baseURL/모델 env만 바꾸는 **소량 작업**이 필요하다(이 범위만 떼어 보면 B). OpenAI 호환 엔드포인트(vLLM·Ollama·LM Studio)는 OpenAI 경로가 이미 있어 거의 설정 변경 수준이다.
- **근거가 되는 결정적 사실**:
  - decision_log_rc2 **#2 = "Gemini 2.5 Flash-Lite 기본·교체가능 / 모델 하드코딩 금지 / 사후 교체가 기술적으로 안전"** (spec01 작업보고 §2·§9).
  - 중계 워커 `chat-proxy`가 **provider/model env 교체가능, Gemini/OpenAI/Anthropic, stream+usage 반환**으로 구현(spec01 §3).
  - **실증 선례**: AI 브리프가 **Claude Sonnet 4 → OpenAI gpt-5.4-mini 로 운영 전환 완료**(Claude는 fallback 유지). 워커에 provider 어댑터를 두어 응답을 공통 형식으로 정규화 → 클라이언트 파서 무손상. 즉 "provider 교체"가 이미 한 번 실제로 일어났다.

---

## 2. 확인한 파일/위치

본 점검에서 **직접 열람한 KB 문서**(dallog-tools 레포 내부 경로):

| KB 문서 | 역할 |
|---|---|
| `knowledge/pacelog-archive/pages/02-workreport/86_…RC2-spec01-기반골격.md` | 코칭AI 토대: 모델 연동(chat-proxy)·원가로그·단가표·캐싱 구조·의사결정 |
| `…/02-workreport/83_…RC2-spec02-운동데이터주입엔진.md` | 운동기록 주입 엔진(14일 스냅샷+여정+확장조회+압축) = 메모리/데이터 로딩 |
| `…/02-workreport/89_…RC2-Phase0-최종검수.md` | RC2 총괄검수. 코드 `file:line` 대조 + 라이브 검증 + 명세 대비 구현표 |
| `…/02-workreport/65_…AI브리프-OpenAI라인전환.md` | 브리프 백엔드 Claude→OpenAI 전환(provider 어댑터·fallback) 실증 |
| `…/pages/99-archive/assets-260605-archive/260527_ai_brief_claude_to_gpt_plan.md` | 브리프 provider 마이그레이션 기획서(아키텍처·결합지점 진단) |
| `…/05-status/91_기능설명서_AI에게보내기.md`, `92_기능설명서_AI브리프.md` | 사용자 관점 기능 정의(교차 참고) |

**위 KB 문서가 인용한 본레포 경로**(본 점검에서 직접 열람한 것이 아니라, KB 기록에 적힌 코드 위치 — 최종 확인 시 대조 대상):

- 중계 워커: `workers/chat-proxy/{index.js,wrangler.toml}`, `workers/brief-proxy/`(브리프), `dallog-brief-proxy`(배포명)
- 코칭 라이브러리: `src/lib/coachChat/` 약 20파일 — `chatApi.ts`(워커 호출), `types.ts`(`ChatProvider`·`QuestionType`), `systemPrompt.ts`(`assembleCoachChat`·`FIXED_SYSTEM_PROMPT`), `pricing.ts`(`calculateEstimatedCost`), `requestLog.ts`(`coach_request_log`), `injection.ts`/`snapshot.ts`/`journey.ts`/`expansion.ts`/`compression.ts`(주입 엔진), `memoryProfile.ts`, `storageGate.ts`, `entitlement.ts`
- 브리프 클라이언트: `src/lib/briefApi.ts`, 파서 `extractProseBody`·`parseVisualBrief`
- DB: `migrations/2026-06-06_coachchat_foundation.sql` 외 RC2 마이그레이션 3종

---

## 3. 현재 모델 호출 구조

- **2개의 독립 AI 파이프라인**이 있다.
  1. **코칭AI(코치노트 AI, RC2)** — 멀티턴 대화. 중계 워커 **`chat-proxy`** 경유. **기본 Gemini 2.5 Flash-Lite**, 키 미등록 시 **OpenAI 폴백**. 워커가 **Gemini/OpenAI/Anthropic 3社** 경로를 갖고 stream+usage를 반환.
  2. **AI 브리프** — 단발 요약/브리프. 중계 워커 **`brief-proxy`(dallog-brief-proxy)** 경유. **기본 OpenAI gpt-5.4-mini**, **Anthropic(Claude) 폴백**.
- **호출 흐름(코칭AI)**:
  `src/lib/coachChat/chatApi.ts` → `chat-proxy` 워커 → 선택된 provider API. 프론트는 키를 모르고 워커만 키를 쥔다(키 노출 0).
- **모델 선택값이 정해지는 곳**: **코드가 아니라 워커 환경변수**. spec01이 "모델 하드코딩 금지 / `resolveProvider`·`resolveModel`로 env 해석"을 완료정의(§8)로 PASS 처리. 브리프 쪽도 `DEFAULT_BRIEF_PROVIDER`·`DEFAULT_BRIEF_MODEL`·`FALLBACK_*` env로 통제(전환 작업보고에서 확인).
- 즉 **모델·provider가 코드 곳곳에 흩어져 있지 않다.** 진입점이 워커 1곳으로 단일화되어 있고, 선택은 설정값이다.

---

## 4. provider/model 컬럼 또는 설정 확인

- **설정(env)**: 코칭 `chat-proxy`는 provider/model을 env로 받음. 브리프는 `DEFAULT_BRIEF_PROVIDER/MODEL`, `FALLBACK_BRIEF_PROVIDER/MODEL`(예: `openai`/`gpt-5.4-mini-2026-03-17`, 폴백 `anthropic`/`claude-sonnet-4-…`)로 **런타임 선택 + 즉시 롤백**까지 설계됨.
- **DB 컬럼**: `coach_request_log`에 **`provider` 컬럼 + `pricing_snapshot_id`**가 있다. 또한 **`coach_price_table`(단가표)** 은 단가 변경 = 새 `price_version` row 추가(기존 row 수정 금지) 구조 → **모델/단가가 데이터로 관리**된다.
- **단순 로그용인가, 런타임 선택용인가**:
  - `coach_request_log.provider` = **기록(감사)용** — 어떤 provider로 처리했는지 append-only 로그.
  - 실제 **런타임 모델 선택은 워커 env**가 담당. DB 컬럼은 그 결과를 추적.
  - `coach_price_table.price_version` = **런타임 원가계산에 사용**(`get_active_coach_price` RPC → `calculateEstimatedCost`). 모델을 바꾸면 새 단가 row만 추가하면 원가 추적이 자동 따라온다.
- **판단**: env(런타임 스위치) + DB(provider 기록 + 단가 버저닝)가 결합돼 있어, 스위칭이 "기록·원가·감사"까지 일관되게 따라오는 **스위칭 설계의 일부**로 볼 수 있다.

---

## 5. 프롬프트/코칭 규칙 구조

- **시스템 프롬프트 위치**: `src/lib/coachChat/systemPrompt.ts`의 **`FIXED_SYSTEM_PROMPT`(VERSION `rc2-03-guardrail`)** — 코드 상수 + **버전 태깅**. 화면 곳곳 산재가 아니라 한 곳에 모여 있고, `assembleCoachChat`이 **고정 system prompt를 선두**(캐싱 대상)에 두고 그 뒤에 가변 데이터(주입블록·메모리 카드)를 붙인다.
- **가드레일/코칭 규칙**: spec03에서 **의료면책·코치스코프·데이터없음·자해분기 4종 + app_usage scope**를 시스템 지시문 텍스트로 정의. **프롬프트 텍스트 기반**이며 특정 provider의 function/JSON 스키마에 의존하지 않는다.
- **메모리 로딩 규칙**: `memoryProfile.ts`(사용자 수동 카드) + 주입 엔진(spec02). 모두 **프롬프트에 텍스트로 주입**되는 방식.
- **모델 교체 시 호환성 위험 지점**:
  - 시스템 프롬프트가 **provider 중립 자연어 텍스트**라 호환성 위험은 낮다. 다만 **톤·한국어 코칭 품질은 모델마다 달라질 수 있다**(브리프 전환 기획서가 "한국어 톤 일관성 리스크"를 명시). → 교체 시 프롬프트 미세조정 + 품질 체감 테스트 필요(파괴적 비호환은 아님).
  - 프롬프트가 **캐싱 친화 구조**(고정 선두)라, provider별 prompt-caching 동작 차이(암묵/명시적 캐싱 지원 여부)는 **원가 최적화에만** 영향 — 기능은 안 깨진다.

---

## 6. 응답 파싱/출력 포맷 종속성

- **코칭AI**: 워커가 **stream + usage**를 반환하도록 표준화. 대화 응답은 **자연어 텍스트(마크다운)** 중심이라 provider별 응답 스키마 차이를 워커가 흡수하면 클라이언트는 텍스트만 받는다.
- **function calling / JSON mode / tool calling 의존 없음**: KB 전 문서에서 코칭AI가 tool/function-calling·JSON mode를 강제 사용한다는 기록이 없다. `question_type` 자동분류도 **LLM이 아니라 rule-based(또는 Phase1 이연)** 로 설계됨(spec01 §11, spec02 결정) → **provider 고유 구조화출력 기능에 묶이지 않음**.
- **브리프(시각화 카드)**: `parseVisualBrief`·`extractProseBody`로 응답을 파싱한다. 여기엔 출력 포맷 결합이 존재했으나, **provider 어댑터가 응답을 공통(Anthropic Messages) 형식으로 정규화**해 파서를 건드리지 않고 Claude→GPT 전환을 끝냈다(실증). → 포맷 종속은 **워커 어댑터 계층에서 해소**하는 패턴이 이미 자리잡음.
- **교체 시 깨질 가능성이 높은 지점**: (a) 새 provider의 스트리밍 청크 포맷 → 워커 정규화 필요, (b) usage(토큰) 필드명 차이 → 원가계산 매핑 필요, (c) 브리프 시각화 카드의 구조적 출력 → 어댑터 정규화 유지 필요. 모두 **워커 계층 국소 작업**으로 흡수 가능.

---

## 7. 메모리/기록 로딩 구조

- **구현 위치**: 전부 **달록 자체 클라이언트 로직**(`src/lib/coachChat/` 주입 엔진). spec02가 신규 테이블·RPC 없이 **기존 테이블만 읽어**(body_records·running_logs·strength_logs/exercises/sets·coach_notes) 다음을 조립:
  - **최근 14일 스냅샷**(체성분·러닝·근력·통증/회복·루틴 5항목)
  - **전체 여정 요약 7축**(시작점·현재·누적변화·최고/최저·정체구간·통증이벤트·루틴변경)
  - **반자동 확장조회**(명확=자동 / 애매=사용자확인 / none, rule-based)
  - **반복패턴 압축 + 변화점 이벤트**
- **모델 독립성**: 이 엔진은 운동기록을 **문자열로 정리해 프롬프트에 주입**할 뿐, 어떤 LLM을 쓰는지 전혀 모른다. 흐름: `테이블 → injection.ts → assembleCoachChat → chatApi.ts → chat-proxy`. **완전히 모델 비종속.**
- **상시 메모리/장기기억**: 14일(최근) + 여정(장기) + 확장조회(필요 시 더 깊이) + 사용자 수동 메모리 카드(`memory_profile`). 모두 달록 DB 기반.
- **판단**: 모델을 바꿔도 **사용자 기록·장기 메모리·코칭 맥락 로딩은 1도 영향받지 않는다.** 메모리는 LLM이 아니라 달록이 소유·조립한다.

---

## 8. 데이터 주권 점검

- **원천 데이터 저장처**: 체중·러닝·근력·식사·통증/회복·코치노트 등은 **달록 Supabase(한스브릿지 관리)** 에 저장, **RLS(본인만)** 적용. 외부 LLM에는 **그때그때 조립된 텍스트 블록**만 전달된다.
- **코칭 히스토리/대화 원문**: 저장 구조(4테이블 `coach_conversation/message/storage_setting/memory_profile` + RLS + 30개 보관한도 + 2단계 삭제)는 **달록 DB에 구현**돼 있다. 단 현재 **`storageGate` OFF**(개인정보 보호장치 5종 완비 전 비활성) → **현시점엔 대화 원문이 아예 저장되지 않는다.** 활성화돼도 저장처는 달록 DB.
- **외부 LLM에 종속되는 데이터**: 없음(구조상). LLM은 **무상태 판단 엔진**으로만 호출되고, 원천 데이터·코칭 맥락·히스토리는 달록 내부에 남는다. (단, 외부 API에 전송된 프롬프트가 해당 업체 정책에 따라 일시 처리·로깅될 수 있음은 일반적 사실 → §11 P1에 데이터처리 약관/무학습 옵션 점검 제안.)
- **개선 필요점**: (a) 각 provider의 **데이터 비학습(no-training)·무보존 옵션** 계약 확인, (b) 전송 payload 최소화 원칙 문서화(이미 user_id 기준 본인 데이터만 포함), (c) storageGate 활성화 시 민감(건강)정보 처리방침 연동.

---

## 9. 자체호스팅/오픈소스 모델 전환 가능성

- **현재 구조에서 가능한가**: **가능.** 모델 진입점이 워커 1곳 + provider/model이 env라, 자체 추론 서버를 "또 하나의 provider"로 추가하는 방식이 자연스럽다.
- **OpenAI 호환 경로 재사용**: 워커에 **OpenAI provider 경로가 이미 있으므로**, vLLM/Ollama/LM Studio/자체 inference가 **OpenAI 호환 API**를 노출하면 **baseURL(엔드포인트) env만 자체서버로 바꾸고 모델명만 교체**하는 수준으로 붙일 여지가 크다. (단, 워커가 OpenAI baseURL을 하드코딩하지 않고 env로 받는지 = **본레포 확인 포인트**.)
- **필요한 추가 계층**:
  - 워커에 `baseURL`(엔드포인트) env가 없다면 추가(P1).
  - 비표준 자체모델은 **provider 어댑터 1개 추가**(요청/응답·스트리밍·usage 정규화). 브리프에서 검증된 어댑터 패턴 그대로 재사용.
  - 자체호스팅 시 **usage/토큰 단가**는 `coach_price_table`에 self-host용 `price_version`(원가 ≈ 인프라비) 추가로 흡수.
- **결론**: 자체호스팅 전환은 **아키텍처 변경이 아니라 워커 어댑터/엔드포인트 env 추가**라는 국소 작업이다. 구조가 이미 이 방향을 허용한다.

---

## 10. 종속 지점 목록

| 종속 지점 | 위치/파일(KB 인용) | 현재 위험도 | 개선 방향 |
|---|---|---|---|
| 모델/ provider 선택 | `chat-proxy` env, `brief-proxy` env (`DEFAULT_*`/`FALLBACK_*`) | 낮음 | 이미 env 스위치 — 유지 |
| 중계 워커의 provider 어댑터 | `workers/chat-proxy/index.js`, `dallog-brief-proxy` | 중간 | 코딩된 3社 밖 신규/자체모델은 어댑터 1개 추가 필요 |
| 응답 스트리밍/usage 포맷 | 워커 정규화 계층 | 중간 | provider별 청크·usage 필드 매핑(워커 국소) |
| 브리프 구조적 출력 파서 | `parseVisualBrief`·`extractProseBody` + 워커 어댑터 | 중간 | 어댑터 정규화 유지로 파서 불변(검증된 패턴) |
| baseURL(엔드포인트) 고정 여부 | `workers/chat-proxy/*`(미확인) | 중간 | env화 안 돼 있으면 자체호스팅 위해 env 추가 |
| 한국어 코칭 톤/품질 | `FIXED_SYSTEM_PROMPT`(provider 중립) | 중간 | 교체 시 프롬프트 미세조정 + 품질 체감 테스트 |
| 시스템 프롬프트/가드레일 | `systemPrompt.ts`(코드 상수·버전태깅) | 낮음 | 텍스트 기반 — provider 비종속(유지) |
| 메모리/데이터 주입 엔진 | `src/lib/coachChat/injection·snapshot·journey…` | 낮음(없음) | 모델 비종속 — 변경 불필요 |
| 원천 데이터/히스토리 저장 | 달록 Supabase + RLS | 낮음(없음) | 달록 소유 — 데이터 주권 확보 |
| provider 데이터 비학습 정책 | (계약/약관, 코드 밖) | 중간 | 각 업체 no-training·무보존 옵션 확인 |

---

## 11. 개선 제안 (코드 수정 없이 제안만)

**P0 (지금/곧, 종속성 직접 관련):**
- **P0-1**: 본레포에서 `workers/chat-proxy/index.js`의 **provider 분기·`resolveProvider/Model`·baseURL env화 여부**를 1회 직접 대조 확인(본 점검은 KB 근거 → 코드 최종 확인). 특히 OpenAI baseURL이 하드코딩인지 env인지.
- **P0-2**: 코칭AI **실응답 라이브 검증**(현재 chat-proxy 배포·키 의존으로 N/A). gemini-2.5-flash 실응답은 별도 보고에서 200 확인됨 → /coach에서 1턴 실송신·원가로그 1행·provider 기록까지 확인.

**P1 (스위칭 견고화):**
- **P1-1**: `chat-proxy`에 **baseURL(엔드포인트) env** 도입(없다면) → vLLM/Ollama/자체서버를 OpenAI 호환으로 즉시 연결.
- **P1-2**: **provider 어댑터 인터페이스 문서화**(요청·응답·스트리밍·usage 정규화 계약). 브리프에서 쓴 패턴을 코칭에도 공통 규약으로 명시 → 신규 provider 추가 비용 최소화.
- **P1-3**: 각 provider **데이터 비학습/무보존 옵션** 확인·기록(데이터 주권 §8). 전송 payload 최소화 원칙 문서화.
- **P1-4**: `coach_price_table`에 **provider별 price_version 운영 가이드**(모델 교체 = 새 row) 명문화.

**P2 (선택·최적화):**
- **P2-1**: **모델 라우팅**(질문유형/난이도별 저가↔고급 모델 분기) 검토 — 브리프 기획서의 "부분 라우팅 ROI" 통찰 재사용.
- **P2-2**: provider별 **prompt-caching 지원 차이**를 원가 추이로 측정해 기본 모델 재조정.
- **P2-3**: 자체호스팅 PoC(OpenAI 호환 서버 1대 → baseURL만 전환)로 전환 가능성 실측.

---

## 12. 최종 판단

- **"지금은 외부 API 임대 유지 + 향후 트리거 발생 시 교체/자체호스팅 가능"** 관점에서, 달록 코칭AI는 **그 요건을 이미 충족하는 구조**다.
  - 모델 진입점이 워커 1곳으로 단일화 + provider/model이 env 스위치 + 모델 하드코딩 금지 원칙.
  - 코칭의 본질(메모리·여정·압축·가드레일·원천 데이터·히스토리)은 **달록 자체 자산**으로 모델과 분리 → LLM은 교체 가능한 판단 엔진.
  - **실증 선례**(브리프 Claude→GPT 운영 전환, fallback 유지, 클라이언트 무손상)가 이 설계가 말뿐이 아님을 증명.
- **권고**: 당장은 비용방어 기본값(Gemini Flash 계열) 임대 유지가 합리적. 트리거(가격 급변·품질 이슈·데이터 주권 요구·규모의 경제)가 오면, **워커 어댑터/엔드포인트 env 추가**라는 국소 작업으로 OpenAI/Anthropic/Gemini 간 전환은 즉시, xAI·자체호스팅은 소량 작업으로 전환 가능하다.
- **단서 재확인**: 본 판정은 KB 문서(코드 file:line 대조 기록 포함) 기반이다. **P0-1(워커 코드 직접 확인)** 을 거치면 등급 A를 코드 레벨로 확정할 수 있다.

---

> **점검 메모(민감정보)**: 본 점검에서 API 키·service_role 키 **원문은 발견되지 않았다**(KB·코드 모두 "[환경변수 등록됨]"·"키 원문 미기록"으로 마스킹). 본 보고서에도 키 값·계정 식별자 원문은 적지 않았다(워커 서브도메인·Supabase 프로젝트 ref 등은 일반화 표기).
