JunkBox libs/shared-ai 가이드
1. 문서 목적
- 이 문서는 JunkBox 전체에서 공통으로 사용하는 AI 런타임 구조와 현재 체스 착수, Jeevtrap AI 기능, 개발 문서 벡터 캐시 지원 구조를 설명하는 가이드임.
- 본 문서는 계획 문서가 아니라 현재 런타임 기준의 구조, 책임 경계, API 계약, 제약 사항을 이해하기 위한 기준 문서임.
2. 라이브러리 역할
shared-ai는 모든 앱이 공통으로 사용하는 AI 연계 라이브러리임.- provider 어댑터, 기능별 모델 라우팅, usage log 기록, AI 통신 오류 정규화, direct/graph runner, prompt registry, graph registry, 체스 착수 그래프 실행을 담당함.
- workout 앱의 운동 계획 생성은 현재
shared-ai런타임 책임에 포함되지 않음. - 현재 체스 샌드박스 기능은
chess-api가 이 라이브러리의 graph runner를 사용해 실행함. - 현재
jeevtrap는 이 라이브러리의 direct runner를 사용해/md,/북마크, 평문 명령 의도 판별 기능을 실행함.
3. 상위 구조
- 체스 착수 공개 진입점은
chess-api의 Lichess challenge API와 WebSocket 로그 화면임. - 체스 착수 실행 주체는
chess-api임. - 그래프 실행, provider 호출, structured output, usage log 기록은
libs/shared-ai가 담당함. - workout 앱의 운동 계획 생성, 운동 기록 조회, 운동 계획 저장은
workout-api의 규칙 엔진과 Workout SQLite 저장소가 담당함.
4. 패키지 구조
junkbox_shared_ai/
├── dev_doc_sync.py
├── graph_registry.py
├── providers/
├── prompts/
├── registry.py
├── runners.py
├── models.py
├── routing.py
├── service.py
├── chess_agent.py
└── __init__.py
5. 공개 API와 내부 API
5.1 공개 API
POST /api/chess/challengesGET /api/chess/gamesPOST /api/chess/games/{game_id}/resignGET /api/chess/game-historyGET /api/chess/games/{game_id}/logsGET /api/chess/ws/logs
5.2 내부 API
GET /api/internal/master-data/codes/{code_type_code}GET /api/internal/master-data/metas/by-key/{meta_key}
5.3 호출 방향
chess-api는 Lichess Bot API와 연동하며, 체스 착수 AI 호출은shared-aigraph runner와 LiteLLM proxy를 통해 수행함.jeevtrap는 Discord 이벤트와 command service에서 direct runner를 호출함.- workout 앱은 운동 계획 생성을 위해
shared-ai또는hub-apiAI 엔드포인트를 호출하지 않음.
6. 주요 책임
6.1 provider 어댑터
LiteLlmProvider는 LiteLLM proxy의 OpenAI 호환 chat completions API 호출을 담당함.- 현재 런타임 provider는
litellm단일 provider만 사용함. - provider 구현은
AiProviderRequest,AiProviderResponse계약을 공유함. - 어떤 provider 어댑터를 사용할지는
model이 아니라 라우팅 YAML의provider값이 결정함.
6.2 개발 문서 벡터 캐시 지원
dev_doc_sync.py는vault/docs/**/*.md문서를vault스키마의 문서 벡터 캐시로 동기화하기 위한 공통 유틸을 제공함.- 이 모듈은 애플리케이션 런타임 요청 처리 경로가 아니라 수동 빌드/인프라 타임 스크립트에서 사용됨.
- 문서의 Source of Truth는
vault/docs/**/*.md파일이며, JunkBox 개발 가이드의 Source of Truth는vault/docs/junkbox/**/*.md파일임. - 벡터 DB는 AI 에이전트의 검색 효율과 컨텍스트 선별을 위한 파생 캐시임.
scripts/sync_dev_docs.py는dev_doc_sync.py의 동기화 진입점임.scripts/search_dev_docs.py는 동일한 임베딩 설정으로 query를 벡터화하고vault.doc_embeddings,vault.doc_chunks,vault.docs에서 관련 chunk를 검색하는 진입점임.scripts/fetch_master_data.py는 AI 에이전트가 hub-api의 통합 master-data snapshot을 조회하는 진입점임.- 이 스크립트들은 애플리케이션 런타임 env인
.env.local,.env.prod,.env.shared를 로드하지 않음. - 이 스크립트들은
scripts/.env.ai만 설정 입력으로 사용함. scripts/.env.ai.template는 개발 문서 동기화/검색과 AI master-data 조회에 필요한 환경 변수 예시를 제공함.scripts/.env.ai는 Git 추적 대상이 아닌 작업자별 비밀 설정 파일임.- master-data 조회에는
FASTAPI_BASE_URL과AI_ACCESS_TOKEN을 사용함. AI_ACCESS_TOKEN은ROLE_AIJWT이며scripts/fetch_master_data.py가 Bearer 토큰으로 전달함.- 문서 동기화와 검색은
VAULT_DATABASE_URL,AI_EMBEDDING_BASE_URL,AI_EMBEDDING_API_KEY,VAULT_EMBEDDING_MODEL을 사용함. AI_EMBEDDING_BASE_URL은 LiteLLM proxy 같은 OpenAI 호환 embeddings API의/v1base URL임.VAULT_EMBEDDING_MODEL은 LiteLLM에 등록된 embedding model id 또는 alias임.- 저장 벡터 차원은 1536임.
vault.docs.doc_namespace는 문서 최상위 분류를 나타내며 JunkBox 개발 가이드는junkbox값을 사용함.vault.docs.doc_kind는 JunkBox 문서에서project_common_guide,project_lib_guide,project_app_guide로 세분화됨.vault.docs.project_key는 JunkBox 개발 가이드에서junkbox값을 사용함.
6.3 기능별 라우팅
- 기능별 provider/model 선택은
AiRoutingStore가 담당함. shared-ai는 라우팅 파일을 해석하는 라이브러리이며 실제 라우팅 정책 파일을 소유하지 않음.- 라우팅 Git 원본 소스 오브 트루스는
apps/hub-api/var/master-data/ai-routing.yml계열 YAML 파일임. - 컨테이너 런타임 경로는
/app/var/master-data/ai-routing.yml이며hub-api,jeevtrap가 같은 파일을 공유함. - 운영 배포는 호스트 공통 경로
/sorc001/junkbox/master-data/ai-routing.yml에 파일을 동기화한 뒤 컨테이너 볼륨으로 마운트하는 구조임. route_key단위로 provider, model, temperature, top_p, max_output_tokens를 결정함.structured_output_mode는 route별 구조화 응답 요청 전략을 결정함.- 현재 지원 값은
auto,json_object,json_schema,prompt_json임. auto는 shared-ai provider adapter가 모델 계열에 맞는 기본 전략을 선택함.- 체스 sleepzz-bot 착수 생성과 두 줄 thought 작성은
CHESS_MOVEroute key를 사용함. - 모든 AI route의 provider 값은
litellm을 사용함. JEEVTRAP_MD는 기본 모델로openrouter/deepseek/deepseek-v4-flash를 사용함.JEEVTRAP_BOOKMARK는 기본 모델로openrouter/deepseek/deepseek-v4-flash를 사용함.JEEVTRAP_COMMAND_INTENT는 기본 모델로openrouter/qwen/qwen-2.5-7b-instruct를 사용하며prompt_json전략, 320 output token 예산, 낮은 temperature와 top_p를 사용함.CHESS_MOVE는 기본 모델로openrouter/qwen/qwen-2.5-7b-instruct를 사용하며prompt_json전략, 320 output token 예산, 낮은 temperature를 사용함.CHESS_MOVE는 짧은 구조화 응답을 전제로 프롬프트 기반 JSON 출력과 낮은 temperature를 사용함.CHESS_MOVE의max_output_tokens는 짧은 두 줄 thought와 단일 choice 번호 응답을 안정적으로 담을 수 있는 범위로 유지함.APP_PROFILE값이 있으면ai-routing.{profile}.yml을 우선 선택하고, 없으면 기본ai-routing.yml을 사용함.- 라우팅 결과의
provider와model은 usage log 저장 시provider:model형식의 full model id로 조합됨.
6.4 usage log 기록
- 공통 AI 사용 이력은
AiUsageLogEntity를 사용함. - 저장 대상은 Hub SQLite
tb_ai_usage_logs_n임. - 성공 시 출력 텍스트, 토큰 수, USD 기준 추정 비용, 실행 시간, 입력 파라미터를 기록함.
- 추정 비용 계산은 full model id와
AI_MODEL.common_code를 직접 비교해 단가 공통코드를 찾는 방식임. AI_MODEL.common_code는provider:model형식이며, 단가 계산 계층은 공통코드 값을:기준으로 쪼개어 provider와 model을 별도 식별하지 않음.AI_MODEL.attribute01은 입력 토큰 USD 단가,AI_MODEL.attribute02는 출력 토큰 USD 단가이며 둘 다 100만 토큰 기준 숫자값으로 해석함.- 실패 시 원본 예외와 사용자 메시지, 정규화 detail을 함께 기록함.
- structured output 파싱이나 검증이 뒤에서 실패해도 provider 호출 시도 자체는 감사 로그로 남아야 하므로,
generate_content()는 usage log row 생성 직후 성공/실패 로그를 즉시 commit함. - 앱 레벨
asyncio.wait_fortimeout처럼 provider 호출 task가 취소되는 경우에도 실패 usage log를 남기기 위해CancelledError를 별도로 기록하고 다시 전파함.
6.5 공통 오류 정규화
- 앱은 provider별 예외를 직접 해석하지 않음.
SharedAiService가 timeout, connection, provider unauthorized, rate limit, provider unavailable, invalid response를 공통CustomException으로 정규화함.- 사용자 노출 메시지는 호출 지점의 한국어 문구로 직접 작성하고, 문장형 메시지는 하십시오체와 마침표 규칙을 따름.
6.6 runner/registry 계층
SharedAiRuntimeFactory는 settings, routing store, master-data store, registry 인스턴스를 묶는 중앙 런타임 팩토리임.DirectAiRunner는 단발성 프롬프트 호출 런타임임.GraphAiRunner는 상태 기반 워크플로우 런타임임.PromptRegistry는 프롬프트 키 단위 관리 지점을 제공함.GraphRegistry는 graph key 단위 실행기 등록 지점을 제공함.- 앱은 provider 생성, usage log 저장, route lookup, 예외 정규화를 직접 수행하지 않고 runner를 통해 공통 처리함.
6.7 workout 계획 생성 비대상
- workout 앱의 운동 계획 생성은
shared-aigraph runner 대상이 아님. - workout 계획 생성은
WORKOUT_PLANAI route, LangGraph workflow, structured output, SSE trace, AI usage log 저장 경로를 사용하지 않음. - workout 계획 생성 규칙은
workout-api의 도메인 서비스와 Workout SQLite 저장소 계약으로 관리함.
7. direct runner 계약
7.1 입력 계약
- 앱은
DirectAiRequest를 만들어DirectAiRunner.run()을 호출함. - 주요 필드는
route_key,module_code,feature_code,system_prompt,user_prompt,output_model,sender_id,channel_id,input_params임. output_model은 Pydantic 모델 기준 structured output 검증 계약임.
7.2 출력 계약
- 반환 타입은
DirectAiRunResult[T]임. data에는 검증된 Pydantic 출력 모델이 들어감.generation에는usage_log_id,text,full_model_id,prompt_tokens,output_tokens,total_tokens가 포함됨.
7.3 호출 흐름
route_key로 라우팅 설정 조회provider기준 provider 인스턴스 해석- provider 요청 실행
- structured output 검증
- usage log 저장
- 앱 서비스로 검증된 결과 반환
8. graph runner 계약
8.1 graph registry 구조
- graph registry는
graph_key -> workflow factory매핑을 유지함. - 현재
CHESS_MOVEgraph key가 등록되어 있음. - workflow factory는
GraphWorkflowContext를 받아 graph 실행 객체를 반환함.
8.2 graph runner 구조
- 앱은
GraphAiRunner.run(graph_key=..., payload=...)또는stream(...)을 통해 graph를 실행함. - runner는 graph key 해석, workflow 인스턴스 생성, 공통 context 주입을 담당함.
- graph 내부 구현은 여전히 도메인별 실행기를 사용할 수 있지만, 앱은 이를 직접 생성하지 않음.
9. SharedAiService 계약
9.1 입력 계약
- 앱은
AiRequestSpec을 만들어generate_content()를 호출함. - 주요 필드는
route_key,module_code,feature_code,system_prompt,user_prompt,sender_id,input_params,response_schema임.
9.2 출력 계약
- 반환 타입은
AiGenerationResult임. usage_log_id,text,full_model_id,prompt_tokens,output_tokens,total_tokens를 포함함.
9.3 호출 흐름
route_key로 라우팅 설정 조회provider기준 provider 인스턴스 해석- provider 요청 실행
- usage log 저장
- 앱 또는 그래프 런타임으로 생성 결과 반환
10. Jeevtrap direct AI 흐름
10.1 /md
jeevtrap서비스는AIBOT_MD_INSTRUCTION,AIBOT_MD_USER_TEMPLATE메타를 사용해 프롬프트를 조립함.DirectAiRunner는JEEVTRAP_MDroute key를 사용해 provider/model을 선택함.- 현재 기본 모델 매핑은
litellm:openrouter/deepseek/deepseek-v4-flash계열임. - 반환 JSON은 문서 제목, 파일명, 마크다운 본문, 경로 추천 목록을 포함하는 Pydantic 모델로 검증됨.
10.2 /북마크
jeevtrap서비스는 스크래핑 본문과AIBOT_WEB_ANALYSIS_INSTRUCTION메타를 사용해 프롬프트를 조립함.DirectAiRunner는JEEVTRAP_BOOKMARKroute key를 사용해 provider/model을 선택함.- 현재 기본 모델 매핑은
litellm:openrouter/deepseek/deepseek-v4-flash계열임. - 반환 JSON은 원문 제목, 정제 제목, 요약, 태그 목록을 포함하는 Pydantic 모델로 검증됨.
10.3 평문 명령 의도 판별
jeevtrap서비스는 봇이 멘션된 일반 Discord 메시지만 명령 의도 판별 대상으로 전달함.jeevtrap서비스는 활성 명령 목록, 사용자 요청 문장,AIBOT_COMMAND_INTENT_INSTRUCTION,AIBOT_COMMAND_INTENT_USER_TEMPLATE메타를 사용해 프롬프트를 조립함.DirectAiRunner는JEEVTRAP_COMMAND_INTENTroute key를 사용해 provider/model을 선택함.- 현재 기본 모델 매핑은
litellm:openrouter/qwen/qwen-2.5-7b-instruct계열임. - 반환 JSON은 매칭 여부, 명령 ID, 신뢰도, 판정 근거를 포함하는 Pydantic 모델로 검증됨.
- 매칭 실패 결과는 명령 실행으로 이어지지 않으며, Jeevtrap 설정에 따라 디버그 안내 메시지만 전송할 수 있음.
11. Workout 앱과의 경계
- workout 앱의 운동 계획 생성은 현재 AI 기능이 아님.
workout-api는 운동 계획 생성을 위해shared-ai,hub-apiAI 엔드포인트, LangGraph, LLM provider, structured output 재생성, usage log 기록을 호출하지 않음.workout-ui는 운동 계획 생성 시 SSE trace, AI 조언, usage log id를 화면 계약으로 사용하지 않음.- workout 앱의 운동 계획 규칙, 기준 탑세트 산정, 계획 저장 구조는
docs/junkbox/apps/guide-apps-workout-api.md를 따름.
12. structured output 처리
12.1 공통 structured generation
SharedAiService.generate_structured_content()는 JSON 파싱, Pydantic 검증, JSON 재생성, usage log 누적을 공통 처리함.- Direct runner와 체스 착수 그래프는 같은 structured generation 경로를 사용할 수 있음.
- 앱별 agent는 도메인 payload 정규화가 필요한 경우 normalizer만 전달하고, JSON 파싱/스키마 재시도 정책은 shared-ai에 위임함.
- LangGraph validator 노드는 파싱이 끝난 도메인 객체의 업무 규칙 검증만 담당함.
12.2 LiteLLM adapter
- LiteLLM proxy는 OpenAI 호환 chat completions 형식을 사용함.
response_schema가 있을 때는 route의structured_output_mode와 모델 계열에 따라 response format을 선택함.- Gemini 계열
auto전략은response_format={"type":"json_object","response_schema":...}를 우선 사용함. - 비-Gemini 계열
auto전략은response_format=json_schema를 우선 사용함. json_object,json_schema,prompt_json전략은 route별로 명시할 수 있음.- LiteLLM 또는 하위 모델이 response format 옵션을 거부하면 JSON only 지시문을 강화한 fallback 요청을 1회 재시도함.
- 따라서
ai-routing*.yml에서 provider/model을 교체해도 structured output 계약을 우선 유지하는 구조임. - LiteLLM 응답은 하위 모델에 따라 message content가 문자열이 아닌 배열 또는 구조체 형태로 올 수 있으므로, provider adapter는 응답 content를 안전하게 문자열로 정규화해야 함.
- LiteLLM 경유 모델이 JSON을 중간에 끊어 반환하는 경우를 대비해 JSON 파싱/스키마 검증 실패 시 같은 프롬프트 문맥으로 JSON 재생성을 요청할 수 있음.
13. 체스 착수 그래프 구조
START
-> thinking_node
-> action_node
-> validation_node
-> END
13.1 입력 계약
ChessAgentRequest는game_id,turn_number,fen,legal_moves,board_ascii,pgn,side,opponent_id,invalid_moves를 포함함.fen은 python-chess 기준 현재 보드 상태 문자열임.legal_moves는 현재 보드에서 가능한 UCI 수 목록이며, 프롬프트에서는 choice 번호가 붙은 목록으로 렌더링됨.invalid_moves는 같은 턴에서 sleepzz-bot이 이미 반환했다가 앱 계층 legal move 검증에 실패한 수 목록이며, 재시도 프롬프트의 forbidden move로 사용함.board_ascii는 rank 8부터 rank 1까지 사람이 읽을 수 있는 8x8 보드 표현임.pgn은 앱 계층에서 현재까지의 전체 기보를 전달하지만, 프롬프트 조립 단계에서는 최근 구간으로 축약될 수 있음.- legal moves, FEN, 축약 PGN, 축약 board ASCII, forbidden moves는 sleepzz-bot 착수와 thought 생성을 위한 핵심 프롬프트 입력임.
13.2 출력 계약
- 체스 착수 출력은
thought와choice를 포함함. thought는 실시간 대국 지연을 줄이기 위해현재 상황:과착수 근거:두 줄의 짧은 해설을 기준으로 함.choice는 legal moves choice 목록 안에 있는 정수여야 함.- 앱 계층은
choice를 UCI move로 매핑한 뒤 Lichess Bot API 제출 결정에 사용하며, 다시 legal moves 포함 여부를 검증함. ChessAgentResult는 앱 계층이 UI 로그와 디버깅 payload에 원본 choice 번호를 보존할 수 있도록choice값을 함께 반환함.- JSON 파싱 실패, provider 오류, content 형식 오류가 발생하면 앱 레벨에서 fallback 합법 수 제출과 실패 로그를 처리함.
- 체스 그래프는 실시간 대국 지연을 줄이기 위해 structured output repair 요청을 사용하지 않고, 단일 응답의 JSON 객체를 파싱함.
13.3 노드 책임
thinking_node는 현재 FEN, side, legal moves, 짧은 목표 문장을 바탕으로 sleepzz-bot 착수 문맥을 생성함.action_node는 착수 문맥, legal moves choice 목록, 축약 board ASCII, 축약 PGN을 사용해 최종{"thought": "...", "choice": 1}JSON 응답을 생성하고, choice를 UCI move로 매핑함.validation_node는 매핑된 move가legal_moves안에 있는지 기록용 오류 메시지를 남김.- 최종 Lichess 제출 전 차단 검증은 앱 계층에서 다시 수행함.
13.4 프롬프트 자산
- 체스 착수 기본 프롬프트는
libs/shared-ai/src/junkbox_shared_ai/prompts/chess_move.md에 둠. - 프롬프트는 FEN만 읽는 방식에 의존하지 않고 legal moves, 축약 ASCII 보드, 최근 PGN을 명확히 구분해 전달해야 함.
- 프롬프트는 출력 안정성, 합법수 선택, 체스 선택 원칙, 설명 품질 규칙을 목적별로 구분해 관리함.
- 프롬프트는 실시간 대국 사용성을 고려해 짧은 두 줄 thought와 단일 JSON 객체 출력을 요구함.
- 체스 프롬프트는 앱 코드 안에 긴 문자열로 하드코딩하지 않고 shared-ai prompt 자산에서 관리함.
14. provider 환경 변수
- provider 비밀값과 base URL은
.env.shared+ENV_FILE조합으로 로딩되는shared-coreSettings를 통해 공급됨. - LiteLLM
LITELLM_API_KEYLITELLM_BASE_URL
- 공통 timeout
AI_HTTP_TIMEOUT_SECONDS
15. 현재 제약과 사용 원칙
- 앱은 provider를 직접 생성하지 않고
get_ai_service(), direct runner, graph runner를 사용함. - 앱은 direct 호출과 graph 호출 중 어떤 runner를 쓸지만 선택하고, 실제 런타임 세부 구현은
shared-ai가 담당함. - 기능별 모델 선택은 앱 코드가 아니라 라우팅 YAML이 결정함.
- provider별 예외 메시지를 앱마다 따로 분기하지 않음.
- usage log 저장 책임을 앱 서비스로 분산하지 않음.
PromptRegistry는 현재 구조상 존재하지만 모든 프롬프트 텍스트를 완전히 중앙 등록하는 단계까지는 가지 않음.- OpenAI 호환 API가 아닌 새 공식 gateway를 추가할 때는 별도 provider 어댑터 구현이 필요함.
- 현재 AI route는 모두
litellmprovider를 사용하며, 모델 선택은 LiteLLM model id를 라우팅 YAML의model값으로 지정해 수행함. - usage log의 full model id는
litellm:model형식을 유지해야 함. - 체스 수 제출 안정성이 중요하므로 앱 계층에서 sleepzz-bot
choice를 UCI move로 매핑한 뒤move in legal_moves검증을 반드시 수행해야 함. - sleepzz-bot 착수 생성 실패 또는 1회 재시도 후 합법 수 검증 실패 시 fallback 합법 수를 제출함.
- 개발 문서 벡터 캐시 동기화와 검색은 사용자 요청 처리용 AI runner 흐름과 분리됨.
- 개발 문서 벡터 캐시 검색 결과는 후속 AI 코딩 작업의 컨텍스트 선별에 사용되며, 원본 문서 수정은 항상
vault/docs/junkbox/**/*.md파일에서 수행함. - 개발 문서가 삭제되면 동기화 스크립트는 DB row를 hard delete하지 않고
vault.docs.is_deleted=true로 표시함. - 개발 문서가 수정되면 동기화 스크립트는 SHA-256 해시 차이를 기준으로 변경을 감지하고 해당 문서의 chunk와 embedding을 교체함.
- 검색 스크립트는 기본적으로 cosine similarity 기준 상위 chunk를 반환하며, 출력에는
source,route,namespace,kind,project,heading,chunk, 유사도 점수, 본문 preview가 포함됨.
16. 관련 문서
- 프로젝트 전체 구조는
docs/junkbox/common/guide-jb-core.md를 따름. - 공통 설정과 예외 구조는
docs/junkbox/libs/guide-lib-shared-core.md를 따름. hub-api역할은docs/junkbox/apps/guide-apps-hub-api.md를 따름.chess-api역할은docs/junkbox/apps/guide-apps-chess-api.md를 따름.chess-ui역할은docs/junkbox/apps/guide-apps-chess-ui.md를 따름.- 개발 문서 원본과 지식 캐시 운영 원칙은
docs/junkbox/common/guide-jb-core.md를 따름. jeevtrap역할은docs/junkbox/apps/guide-apps-jeevtrap.md를 따름.workout-api역할은docs/junkbox/apps/guide-apps-workout-api.md를 따름.workout-ui역할은docs/junkbox/apps/guide-apps-workout-ui.md를 따름.