API 연동 가이드
외부 시스템에서 PlantPulse AI의 MCP 및 API를 직접 호출하는 방법입니다.
옛 독립 서비스 MCP Server(:50000) · Ontology(:8888) 는 아카이브됐고, LightRAG 를
직접 열던 :7114 도 없습니다. 도구는 MCP 게이트웨이 한 주소에 모입니다 → MCP 도구 지도
MCP 클라이언트는 게이트웨이 한 주소로
외부 MCP 클라이언트(다른 에이전트 · 데스크톱 앱 등)는 https://<호스트>/mcp/gateway/ 하나로 붙습니다(Streamable HTTP).
플랫폼 통합 MCP · 시계열 인사이트 · RAG · 문서 이해 · 비전 · 공학 계산 · 디지털 스레드 · 운영자가 붙인 외부 서버의 도구가 이 주소에 모입니다.
| 할 일 | 자리 |
|---|---|
| 키 받기 | AI 관리 콘솔 › MCP 게이트웨이 › 클라이언트 API 키 에서 키 발급 — 키(ppgw_…)는 발급 직후 한 번만 보입니다. 키에 범위(설비 · 사이트 · 프로젝트 · 고객 PO · 협력사)와 등급을 줍니다 — 범위가 없는 키는 외부 서버 도구만 부릅니다 |
| 볼 도구 정하기 | 같은 메뉴의 권한 — 새 키는 아무것도 허용되지 않은 채 시작합니다. 서버마다 «모든 도구» 또는 일부 도구를 허용합니다 |
| 부르기 | X-API-Key: ppgw_… 또는 Authorization: Bearer ppgw_… |
| 호출 보기 | 모니터링 › 호출 기록·지표 — 키마다 호출이 남습니다 |
- 키가 없거나 틀리면 401, 1분에 20번 넘게 틀린 클라이언트는 그 분 동안 429 입니다.
- 이 경로는 게이트웨이의 MCP(
/mcp/)만 넘깁니다. 게이트웨이 관리 REST(/api/v1)는 바깥에 열리지 않습니다. - 끝 슬래시 없는
/mcp/gateway는/mcp/gateway/로 넘겨 줍니다(308). - 화면 · 권한 · 키 폐기는 → AI 관리 콘솔 — MCP 게이트웨이
그 밖의 엔드포인트
| 서비스 | URL | 프로토콜 | 인증 |
|---|---|---|---|
| MCP 게이트웨이 | https://<호스트>/mcp/gateway/ | MCP (Streamable HTTP) | 게이트웨이 키 ppgw_… — 위 |
| 코파일럿 공개 API | https://<호스트>/api/v1/… | REST | X-API-Key → 아래 |
| LiteLLM (모델) | http://<서버>:4000 | OpenAI 호환 | 마스터 키 |
| 통합 MCP (플랫폼) | <플랫폼>/api/v5/mcp | MCP (JSON-RPC 2.0) | api_key — Authorization: Bearer 또는 X-API-Key |
서비스 직결 — 운영자 · 시스템 연동용
사내 MCP 서버는 게이트웨이를 거치지 않고 직접도 열려 있습니다. 다만 인증이 설치 공용 인바운드 키(PI_INBOUND_API_KEY) 하나라
그 키를 든 쪽은 전권이고, 호출자마다의 도구 권한도 호출 기록도 없습니다.
운영자 스크립트 · 플랫폼 CEP 같은 시스템 연동에만 쓰고, 고객의 MCP 클라이언트에는 게이트웨이를 주십시오.
| 서비스 | 직결 주소 | 인증 |
|---|---|---|
| 시계열 인사이트 | http://<서버>:8970/mcp/ · https://<호스트>/mcp/timeseries/ | 인바운드 키 |
| RAG 관문 | http://<서버>:7115/mcp/ · https://<호스트>/mcp/rag/ | 인바운드 키 |
| 문서 이해 | https://<호스트>/mcp/parser/(호스트 포트 7120 은 미발행) | 인바운드 키 |
| 비전 | https://<호스트>/mcp/vision/(호스트 포트 7130 은 미발행) | 인바운드 키 |
디지털 스레드 · 공학 계산 · 플랫폼 통합 MCP 를 한 주소로 받는 길은 게이트웨이뿐입니다.
기본 호스트의 경로로도 열려 있습니다
서브도메인이나 포트를 따로 열지 않아도, 프록시가 기본 호스트(채팅과 같은 주소)의 경로로 다음을 넘겨 줍니다.
| 경로 | 넘어가는 곳 |
|---|---|
https://<호스트>/mcp/gateway/ | MCP 게이트웨이의 MCP — 외부 MCP 클라이언트는 이것 |
https://<호스트>/llm/ | LiteLLM — OpenAI SDK 의 base_url 은 https://<호스트>/llm/v1 |
https://<호스트>/mcp/rag/ | RAG 관문의 MCP |
https://<호스트>/mcp/timeseries/ | 시계열 인사이트의 MCP |
https://<호스트>/mcp/parser/ | 문서 이해의 MCP |
https://<호스트>/mcp/vision/ | 비전의 MCP |
인증은 뒷단이 합니다 — /mcp/gateway/ 는 게이트웨이 키 ppgw_…, 직결 MCP 넷은 X-API-Key: <PI_INBOUND_API_KEY>, /llm/ 은 LiteLLM
마스터 키가 없으면 401 입니다. 기본 호스트는 대개 바깥 DNS 가 붙은 주소이므로, 설치 뒤
CHANGE_ME_… 로 남은 키가 없는지 반드시 확인하십시오 → 자격증명 관리
LiteLLM 관리 화면(/llm/ui)은 이 경로로 열리지 않습니다 — :4000 으로 여십시오.
뒷단의 인바운드 키
시계열 인사이트 · RAG · 문서 이해 · 비전 · 디지털 스레드는 같은 인바운드 키(설치 env 의 PI_INBOUND_API_KEY)를 씁니다 →
자격증명 관리. X-API-Key: <키> 또는 Authorization: Bearer <키> 로 보냅니다.
-
키 없이 열리는 것은
/ping·/health둘뿐입니다./metrics도 키가 필요합니다. -
키가 없거나 틀리면 401 입니다(RAG 는
missing or invalid API key). -
🔴 키가 비어 있으면 서비스가 뜨지 않습니다(fail-closed). 설치 점검(
bin/lint-env.sh)이 먼저[fail] PI_INBOUND_API_KEY is empty and PI_INBOUND_AUTH_DISABLED is not true로 멈추고, 서비스 로그에는FATAL: … is empty — refusing to start …이 남습니다. 빈 키로 «조용히 열린 뒷단» 은 생기지 않습니다. -
인증을 일부러 끄려면(격리된 시험 환경 등) 설치 env 에
PI_INBOUND_AUTH_DISABLED=true를 적습니다(기본false). compose 가 이 값을 서비스마다의 이름으로 넘깁니다.서비스 컨테이너가 읽는 이름 RAG RAG_INBOUND_AUTH_DISABLED시계열 인사이트 TS_INBOUND_AUTH_DISABLED디지털 스레드 DT_INBOUND_AUTH_DISABLED문서 이해 · 비전 INBOUND_AUTH_DISABLED키가 적혀 있으면 키가 이깁니다(lint-env 가
[note] … is ignored — PI_INBOUND_API_KEY is set and the key wins로 알립니다).true·false밖의 값은[fail]입니다. 끈 채로 뜬 서비스는/health에 그렇다고 적습니다(디지털 스레드auth.inbound=disabled, 시계열 인사이트inbound_auth: "disabled") — 관리 콘솔의 API 키 화면에서 점검 실행 하면 열림 으로 잡힙니다 → AI 관리 콘솔 — API 키
문서 이해·비전은 호스트 포트(7120 · 7130)를 발행하지 않습니다 — 바깥에서는 위의
/mcp/parser/ · /mcp/vision/ 경로로만 닿습니다. 디지털 스레드(7140)도 호스트 포트를 열지 않습니다.
/mcp 가 아니라 /mcp/ 입니다. 슬래시를 빼면 리다이렉트나 404 가 납니다.
MCP 프로토콜 호출
다섯 서버 모두 MCP(Model Context Protocol) 표준을 따릅니다. 공장 데이터·온톨로지
도구는 플랫폼 통합 MCP(/api/v5/mcp), 파형 분석은 시계열 인사이트, 문서·사진은 나머지
셋입니다.
서버를 직접 부를 때는 접두어 없는 이름(analyze_asset)을 씁니다. insight_ 같은
접두어는 채팅의 도구 등록기가 붙이는 것이라, 채팅 화면에서 보는 이름과 여기서 쓰는 이름이
다릅니다.
MCP 기본 구조
MCP는 JSON-RPC 2.0 기반의 HTTP POST 통신입니다. 통합 MCP는 api_key 인증(Authorization: Bearer <token>)이 필요합니다.
POST /api/v5/mcp HTTP/1.1
Content-Type: application/json
Authorization: Bearer <platform.api.token>
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "<도구 이름>",
"arguments": {
"<파라미터1>": "<값1>",
"<파라미터2>": "<값2>"
}
}
}
도구 목록 조회
curl -X POST <플랫폼>/api/v5/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <platform.api.token>" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}'
도구 호출 예시
search_domains 로 시작합니다사람은 이름으로 말하고 도구는 ID 를 받습니다. search_domains 가 14개 도메인을
부분일치로 검색해 «도메인 + ID» 를 확정하고, 그 ID 를 상세 도구에 넘기는 것이 기본
순서입니다.
① 이름으로 찾아 ID 를 얻는다
curl -X POST <플랫폼>/api/v5/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <platform.api.token>" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_domains",
"arguments": {
"query": "압축기",
"domain": "asset",
"limit": 20
}
}
}'
domain 을 알면 지정해 정밀 검색하고, 모르면 생략하면 도메인별로 갈라 돌려줍니다.
② 자산 계층을 펼친다
curl -X POST <플랫폼>/api/v5/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <platform.api.token>" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_asset_tree",
"arguments": { "asset_id": "<①에서 얻은 ID>" }
}
}'
tags 키가 없는 것은 «태그 없음» 이 아닙니다기본적으로 태그는 개수(tag_count)만 옵니다. 이름·tag_id 가 실제로 필요할 때만
include_tags: true 를 주십시오 — 응답이 몇 배로 커집니다. 태그 유무의 정본은
tag_count 이고, tag_count: 0 만이 진짜 태그 없음입니다.
③ 태그 현재값을 한 번에 읽는다
curl -X POST <플랫폼>/api/v5/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <platform.api.token>" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_tag_values",
"arguments": { "tag_ids": ["<tag_id>", "<tag_id>"] }
}
}'
tag_ids 는 배열이고 한 번에 100개까지 넣습니다. 응답의 found: false 는 미등록
태그, error 는 조회 실패(재시도할 가치가 있음)를 뜻합니다. 기간 이력은
get_tag_history 입니다.
통합 MCP 인증
통합 MCP는 api_key 인증이 필수입니다. 채팅이 쓰는 값은 코파일럿의
platform.api.url · platform.api.token(환경변수 CO_PLATFORM_API_URL ·
CO_PLATFORM_API_TOKEN — compose 가 호스트 env 의 PP_API_URL · PP_API_TOKEN 을 옮겨 넣습니다)입니다.
2026.09 까지의 접두사 CH_ 는 2026.10 에 CO_ 로 바뀌었고 폴백이 없습니다.
⚠ 옛 이름 mcp.api.url · mcp.api.token 은 없습니다. 기동할 때 옛 설정 행이 새 이름으로
옮겨지지만, 환경변수를 옛 이름으로 주면 아무 일도 일어나지 않습니다.
인증 헤더
curl -X POST <플랫폼>/api/v5/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <platform.api.token>" \
-d '{ ... }'
온톨로지(지식 그래프) 호출
온톨로지 도구는 통합 MCP 에 흡수되었습니다. 🔴 ontology_* 라는 이름의 도구는
없습니다 — 옛 독립 서비스(:8888) 시절의 이름입니다. 지금은 셋입니다.
| 도구 | 무엇을 답하나 |
|---|---|
get_ontology | 한 사이트의 구성 개요 — 구역/라인/설비 구조 + 설비별 태그 개수 |
query_ontology | 방향성 관계 탐색 — 「X 가 무엇에 영향을 주나」, 상류·하류 |
query_sparql | 다중 조건 교차 질의 (SELECT / ASK 전용) |
이름이 비슷하지만 반환하는 축이 다릅니다. 순수 계층 구조는 get_asset_tree,
사이트 구성 개요는 get_ontology, 관계 전파는 query_ontology 입니다.
사이트 구성 개요
curl -X POST <플랫폼>/api/v5/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <platform.api.token>" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_ontology",
"arguments": { "site_id": "<site_id>" }
}
}'
site_id 는 필수입니다 — search_domains 에 domain: "site" 로 먼저 해소하십시오.
total_tag_count 하나가 정본입니다노드별 tag_count 를 합산하지 마십시오 — 펼치지 않은 가지가 빠집니다.
total_tag_count_unknown: true 는 「셀 수 없었다」는 뜻이므로 추정하지 마십시오.
관계 탐색 — 「이게 무엇에 영향을 주나」
curl -X POST <플랫폼>/api/v5/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <platform.api.token>" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "query_ontology",
"arguments": {
"entity_id": "<설비 ID>",
"direction": "DOWN",
"depth": 2
}
}
}'
| 인자 | 값 | 뜻 |
|---|---|---|
entity_id | (필수) | 탐색을 시작할 엔티티 |
relation_type | FEEDS · DEPENDS_ON · PRODUCES | 생략하면 전체 |
direction | UP · DOWN · BOTH | 기본 DOWN(하류 영향) |
depth | 1 ~ 5 | 기본 2 |
응답은 root + nodes[] + edges[] + truncated 입니다. truncated: true 면 깊이나
폭에서 잘렸다는 뜻이니 범위를 좁혀 다시 묻습니다.
시계열 인사이트 MCP 호출
설비 이상 탐지
curl -X POST http://127.0.0.1:8970/mcp/ \
-H "Content-Type: application/json" \
-H "X-API-Key: <PI_INBOUND_API_KEY>" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "detect_asset_anomaly",
"arguments": {
"asset_id": "ASSET_DJ_M_01_1",
"lookback_minutes": 1440,
"top_k": 5
}
}
}'
threshold 를 생략하면 기본 5.0 입니다. 센서 이상 점수는 0~1 이 아니라 보정 점수
(기준 구간 대비 로버스트 z)라서, 0 을 주면 기준 중앙값보다 조금만 높아도 전부 이상으로 셉니다 →
AI 모델
설비 센서 예측
curl -X POST http://127.0.0.1:8970/mcp/ \
-H "Content-Type: application/json" \
-H "X-API-Key: <PI_INBOUND_API_KEY>" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "forecast_asset",
"arguments": {
"asset_id": "ASSET_DJ_M_01_1",
"prediction_length": 96,
"enable_bounds": true
}
}
}'
설비 건강 점수
curl -X POST http://127.0.0.1:8970/mcp/ \
-H "Content-Type: application/json" \
-H "X-API-Key: <PI_INBOUND_API_KEY>" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_asset_health",
"arguments": {
"asset_id": "ASSET_DJ_M_01_1"
}
}
}'
RAG (LightRAG) 호출
RAG 엔진은 LightRAG 1.5.7(2026.10 부터 우리가 감싼 이미지 pi/plantpulse-lightrag)이지만, 🔴 직접 부르지 않습니다. LightRAG 는 컨테이너
안에만 있고 바깥에서 닿는 자리는 rag-api(7115) 관문 하나입니다.
문서 업로드·목록·삭제는 관리 콘솔에서 합니다(옛 LightRAG WebUI 는 콘솔로 흡수됐습니다).
검색은 관문의 REST 로 부르고 X-API-Key 로 인증합니다.
코파일럿 채팅의 문서 검색은 이 관문의 MCP 도구 rag_search 를 MCP 게이트웨이를 거쳐 부릅니다(코파일럿에
내장 문서 검색은 없습니다). REST /api/v1/query 와 MCP rag_search 는 같은 봉투를 냅니다 — LightRAG 에
직접 붙지 않습니다. 아래 응답 모양은 LightRAG 의 것이 아니라 관문이 정한 봉투입니다.
텍스트 검색
curl -X POST http://127.0.0.1:7115/api/v1/query \
-H "Content-Type: application/json" \
-H "X-API-Key: <PI_INBOUND_API_KEY>" \
-d '{
"query": "펌프 정비 절차",
"mode": "hybrid"
}'
| 인자 | 값 |
|---|---|
query | 검색어 — 3글자 이상(짧으면 400) |
mode | hybrid(기본) · naive · local · global — 그 밖의 값은 422 |
| 응답 | 뜻 |
|---|---|
{"retrieved": true, "answer": …, "sources": [{"file_name": …, "reference_id": …}]} | 찾았습니다. reference_id 는 답 본문의 출처 표식 [n] 의 n 입니다(엔진이 번호를 주지 않은 출처에는 없습니다) |
{"retrieved": false, "code": …, "reason": …, "sources": []} | 못 찾았습니다. answer 키 자체가 없습니다 |
| HTTP 502 | 엔진 쪽 실패 |
retrieved: false 는 오류가 아니라 «없다» 입니다HTTP 는 200 입니다. code 가 NO_CONTEXT 면 색인된 문서에 관련 내용이 없는 것이고,
EMPTY_ANSWER 면 엔진이 빈손으로 돌아온 것입니다. 다시 불러도 같은 답이 옵니다 —
answer 가 없는 응답을 결과처럼 보여 주지 마십시오.
도면 비교 — 두 판의 차이
도면 수집(DRAWING_INGEST_ENABLED=true)을 켠 설치에서 쓸 수 있습니다 → 환경 변수 설정.
입구는 둘이고, 비교는 문서 이해가 합니다. rag-api 는 색인된 두 판의 원본을 꺼내 문서 이해에 넘기고 그 결과를
그대로 돌려줍니다. 둘 다 인바운드 키를 받습니다.
| 입구 | 언제 |
|---|---|
rag-api GET /api/v1/drawings/{asset_id}/{doc_name}/diff | 이미 색인된 도면 — 앞 판을 찾아서 비교합니다 |
문서 이해 POST /api/v1/drawings/diff | 손에 든 파일 두 개를 바로 비교합니다(색인과 무관) |
색인된 도면의 앞 판과 비교 (rag-api)
curl -G "http://127.0.0.1:7115/api/v1/drawings/<asset_id>/<doc_name>/diff" \
-H "X-API-Key: <PI_INBOUND_API_KEY>" \
--data-urlencode "lang=ko"
| 인자 | 뜻 |
|---|---|
against | 비교할 앞 판의 문서 이름. 비우면 같은 도면 번호 가운데 판 표시가 바로 앞인 것(A < B < … < Z < AA, 1 < 2 < 10)을 고릅니다 |
against_asset | 앞 판이 다른 설비에 올라가 있을 때 그 설비 ID (기본 같은 설비) |
lang | summary 줄의 언어 ko · en. 비우면 설치 언어(PP_LANG). 숫자는 언어와 상관없이 도면에 적힌 그대로입니다 |
| 상태 | 뜻 |
|---|---|
404 | 어느 쪽이 색인돼 있지 않거나, 원본이 보관돼 있지 않거나, 고를 앞 판이 없음 |
422 | 비교기가 파일을 읽지 못함 |
502 | 비교기나 객체 저장소에 닿지 못함 |
같은 라우터에 쪽 미리보기(…/page/{n} — PDF 는 PNG, DXF 는 SVG)와 판 상태(…/revision-status — current ·
outdated · change_open · unknown)가 있습니다.
파일 두 개를 바로 비교 (문서 이해)
문서 이해는 호스트 포트를 발행하지 않으므로 바깥에서는 parser.* 호스트로 부릅니다.
curl -X POST "https://parser.<도메인>/api/v1/drawings/diff" \
-H "X-API-Key: <PI_INBOUND_API_KEY>" \
-F "old=@A-100_rev-A.dxf" -F "new=@A-100_rev-B.dxf" -F "lang=ko"
- 파일은
.dxf나 텍스트 층이 있는 벡터.pdf, 하나에 50 MB 까지입니다. DWG 는 받지 않습니다. lang의 기본값은en입니다(rag-api 입구와 다릅니다).- 거절은
error_kind: input과 함께 확장자415· 크기413· 읽을 수 없음400입니다.
결과의 모양
| 키 | 담는 것 |
|---|---|
title | 바뀐 표제란 칸 |
dimensions | added · removed · changed(값 · 단위 · 개수 · 공차 · 글자 · 위치 중 무엇이 바뀌었나) · unchanged |
bom · notes | 바뀐 BOM 행 · 주기 |
regions | 바뀐 영역 — 뒤 판의 bbox 와 앞 판의 bbox_old. 맞추기에 실패하면 status: unavailable 과 이유이고, 영역을 지어내지 않습니다 |
summary | 사람이 읽는 요약 줄 — 숫자는 도면의 글자 그대로 |
confidence · limits | high · medium · low 와, 비교하지 못한 것(스캔 쪽 · 표제란 없음 · 한쪽에만 BOM 등) |
warnings | different_drawing_numbers(도면 번호가 다름) · revision_label_unchanged(판 표시가 같음) 등 |
영상 · 음성 — rag-api 의 /api/v1/media
영상 · 음성 수집을 켠 설치(설치 env PI_MEDIA_ENABLED=true)에서만 붙는 경로입니다. 꺼진 설치에는 이 경로가 아예 없어 404 이고,
영상 · 음성 업로드는 400 으로 거절됩니다. 켜졌는지는 GET /api/v1/config 의 media_ingest_enabled 로 봅니다 →
환경 변수 설정 · AI 관리 콘솔 — 동영상
| 메서드 · 경로 | 하는 일 |
|---|---|
GET /api/v1/media/jobs | 파일마다 처리 상태 · 길이 · 구간 수 · 장면 그림 수(thumb_count)와 처리 시간대(window — open · next_start · spec) |
GET /api/v1/media/jobs/{asset_id}/{doc_name} | 파일 하나의 상세 |
GET /api/v1/media/integrity | 색인됐는데 검색 색인에 없는 구간 |
POST /api/v1/media/jobs/{asset_id}/{doc_name}/retry | 다시 대기열에(실패 · 건너뜀). ?force=true 면 처음부터 다시 녹취 |
GET /api/v1/media/{asset_id}/{doc_name}/transcript | 보관된 녹취록 |
GET /api/v1/media/{asset_id}/{doc_name}/thumb?t=<ms> | 그 시각 이전의 가장 가까운 장면 그림 |
GET /api/v1/media/{asset_id}/{doc_name}/stream | 재생 — Range 요청에 206 으로 답합니다 |
- 올리기는 문서와 같은
POST /api/v1/docs/upload이고, 지우기도 같은DELETE /api/v1/docs/{asset_id}/{doc_name}입니다(플랫폼 원장에서 온 것은409). - 장면 그림(
…/thumb) 은ETag와Cache-Control: private, max-age=86400을 붙입니다. 같은ETag를If-None-Match로 보내면 그림을 다시 읽지 않고 304 로 답합니다. 상태 DB 가 잠시 바쁘면 500 대신 503state database busy, retry shortly와Retry-After: 1입니다 — 그 시간 뒤 다시 부르십시오. - 코파일럿 화면이 쓰는 중계(코파일럿의
/media/thumb·/media/stream, 로그인 세션 인증)도ETag· 304 를 그대로 넘기고, 뒷단의 503 은Retry-After와 함께 503 으로 넘깁니다(전에는 502 로 감쌌습니다).
요청 id 와 지표 — X-Request-ID · /metrics
2026.10 재릴리즈부터 파이썬 뒷단 다섯(RAG · 시계열 인사이트 API · 문서 이해 · 비전 · 디지털 스레드)이 같은 규칙을 씁니다.
- 요청 id — 요청에
X-Request-ID(1~128자, 영문 · 숫자 ·._-)를 실어 보내면 그 값을, 없거나 모양이 틀리면 새로 만든 값을 응답 헤더X-Request-ID로 돌려줍니다. 그 요청이 도는 동안 남는 로그 줄에 같은 값이request_id로 붙습니다. RAG 는 오류 응답 본문에도request_id를 더합니다. - 지표 —
GET /metrics가 Prometheus 텍스트 형식을 냅니다. 인바운드 키가 필요합니다(헬스 경로와 달리 면제가 아닙니다). 포트는 바뀌지 않았습니다 → 로그 및 모니터링
curl -s -D - -o /dev/null -H "X-Request-ID: my-trace-001" \
-H "X-API-Key: <PI_INBOUND_API_KEY>" http://127.0.0.1:7115/api/v1/config | grep -i x-request-id
LLM API 호출 (OpenAI 호환)
LiteLLM 프록시는 OpenAI 호환 API를 제공합니다. 기존 OpenAI SDK를 그대로 사용할 수 있습니다.
LAN 에서는 http://<서버>:4000/v1, 기본 호스트를 거치면 https://<호스트>/llm/v1 입니다.
채팅 완성
curl http://127.0.0.1:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer 설치-시-변경" \
-d '{
"model": "aura-lite",
"messages": [
{"role": "user", "content": "주입기 베어링 교체 주기를 알려줘."}
],
"temperature": 0.7
}'
임베딩 생성
curl http://127.0.0.1:4000/v1/embeddings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer 설치-시-변경" \
-d '{
"model": "aura-embedding-4b",
"input": "주입기 베어링 교체 절차"
}'
모델 목록 확인
curl http://127.0.0.1:4000/v1/models \
-H "Authorization: Bearer 설치-시-변경"
사용 가능한 모델
| 모델 이름 | 용도 | 실제 모델 |
|---|---|---|
aura-lite | 대화 · 분석 (이미지 입력 겸용) | Aura — 기본 Aura-Lite-NVFP4 |
aura-vision | 위와 같은 엔진의 비전용 별칭 | 위와 같은 모델 |
aura-embedding-4b | 임베딩 | Aura-Embedding-4B |
aura-reranker-0.6b | 리랭킹 | Aura-Reranker-0.6B |
gpt-4o · text-embedding-3-large 는 이제 오류입니다예전 판은 OpenAI 호환 별칭(gpt-4o · gpt-4o-mini · text-embedding-3-large ·
rerank-multilingual-v3.0)을 받았습니다. 지금은 받지 않습니다 — 연동 코드에 옛 이름이
남아 있으면 호출이 거절됩니다. 위 표의 이름으로 바꾸십시오. 지금 떠 있는 이름은
/v1/models 로 확인할 수 있습니다.
어느 이름이든 사내에서 도는 Aura 모델로 갑니다 — 폐쇄망 설치에서도 같습니다.
Python SDK 예시
OpenAI SDK로 LLM 호출
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:4000/v1",
api_key="설치-시-변경"
)
response = client.chat.completions.create(
model="aura-lite",
messages=[
{"role": "user", "content": "주입기 상태를 분석해줘."}
]
)
print(response.choices[0].message.content)
MCP 도구 호출
import requests
def call_mcp_tool(url, tool_name, arguments=None, headers=None):
response = requests.post(
url,
headers=headers or {},
json={
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": tool_name,
"arguments": arguments or {}
}
}
)
return response.json()
# 이름으로 설비를 찾는다 (통합 MCP — api_key 필요)
result = call_mcp_tool(
"<플랫폼>/api/v5/mcp",
"search_domains",
{"query": "압축기", "domain": "asset"},
headers={"Authorization": "Bearer <platform.api.token>"}
)
print(result)
# 설비 이상 탐지 (TimeSeries-Insight — 인바운드 키 필요)
result = call_mcp_tool(
"http://127.0.0.1:8970/mcp/",
"detect_asset_anomaly",
{"asset_id": "ASSET_DJ_M_01_1", "lookback_minutes": 1440},
headers={"X-API-Key": "<PI_INBOUND_API_KEY>"}
)
print(result)
코파일럿 공개 API (/api/v1)
외부 시스템이 채팅과 아침 카드를 그대로 쓰는 입구입니다. 채팅과 같은 주소(기본 호스트)에 있습니다.
| 메서드 · 경로 | 하는 일 |
|---|---|
POST /api/v1/chat | 질문하고 답을 받습니다. 필요한 도구는 서버가 고릅니다 |
GET /api/v1/tasks/daily | 그날의 데일리 카드(매일 05:00 생성)를 읽습니다. ?date=YYYY-MM-DD — 공장 시간대 기준, 비우면 오늘 |
GET /api/v1/tools | 에이전트가 지금 쓸 수 있는 도구 목록(이름 · 설명 · 조회전용 여부) |
GET /api/v1/ai-feedback | 사람이 AI 결과에 단 표시(헛경보 · 제안 수락/거절/효과)와 서버가 규칙으로 단 표시(맞음 · 헛경보 · 놓침)를 읽습니다 — 2026.10 (아래) |
curl -X POST https://<호스트>/api/v1/chat \
-H "Content-Type: application/json" \
-H "X-API-Key: <발급받은 키>" \
-d '{ "input_value": "지금 끊어진 엣지 장비가 몇 대야?" }'
응답은 answer(마크다운) · session_id · duration_ms · tools(이 답을 만들며 부른 도구)
입니다. session_id 를 빼거나 비우면 새 대화를 열고(화면의 「새 대화」와 같습니다 — 기본 앱, 부른
사람 소유) 그 id 를 응답의 session_id 로 돌려줍니다. 대화를 이어 가려면 다음 요청에 그 값을 넣습니다.
동기 호출이라 도구를 여러 번 부르는 질문은 수십 초가 걸릴 수 있습니다.
session_id 없는 호출이 500 이었습니다문서는 「비우면 새 대화」라고 했지만 서버가 500 을 냈고, X-API-Key 로 부른 호출도 500 으로 끝났습니다.
2026.10 에서 둘 다 고쳐졌습니다.
2026.10 재릴리즈 3 부터 모델이 다시 시도한 뒤에도 답을 만들지 못하면 HTTP 502 로 돌려줍니다. 본문의 모양은 같고 answer 가 비어 있습니다.
빈 답을 200 으로 받아 «답 없음» 을 결과처럼 보여 주지 않게 한 것입니다 — 같은 질문을 다시 보내면 됩니다.
AI 결과에 사람이 단 표시 — GET /api/v1/ai-feedback
코파일럿의 제안에서 사람이 AI 결과에 단 표시(무시 사유 · 수락/거절 · 효과)를 읽기만 하는 입구입니다(쓰는 길은 화면뿐입니다)
→ AI 발견과 정확도 — 사람이 남기는 판단. 인증은
다른 /api/v1 과 같습니다(X-API-Key 만 — 2026.10 재릴리즈 6 부터 로그인 세션은 403).
curl "https://<호스트>/api/v1/ai-feedback?since=2026-10-01T00:00:00Z&limit=500" \
-H "X-API-Key: <발급받은 키>"
| 인자 | 뜻 |
|---|---|
since | labeled_at 하한(RFC 3339 또는 epoch ms). 비우면 처음부터 |
asset_id | 설비 id 로 거릅니다 |
target_kind | ANOMALY(이상 한 건) · FINDING(AI 발견) · PROPOSAL(발견에서 난 제안 카드) · MISSED(놓침 신고) · DAILY_TASK |
limit | 최대 줄 수 1~1000, 기본 500 |
응답은 count · items · next_since 입니다. 오래된 것부터 오므로 다음 쪽은 next_since 로 다시
부릅니다 — 같은 시각의 줄이 겹쳐 올 수 있으니 feedback_id 로 거르십시오. 같은 대상에 사람이 다시 누르면
그 줄의 value · labeled_at 이 바뀝니다(마지막 표시가 이깁니다).
label_key | value |
|---|---|
verdict | hit · false_alarm · unsure(판정 보류 — 정확도 계산에서 뺍니다) |
reason | 헛경보 이유 setup · idle · maintenance · sensor · other / 제안 거절 이유 already_done · not_needed · wrong · other |
cause | 실제 원인 breakdown · degradation · sensor · setup · maintenance · normal · other |
proposal_decision | accepted · rejected |
outcome | effective · not_effective · unknown (comment 에 메모) |
missed | missed (comment 에 설명, occurred_at 에 놓친 사건 시각) |
새 화면(메뉴 셋)에서 사람이 남기는 값은 verdict 의 false_alarm(확인할 이상의 무시)과 그 reason, proposal_decision, outcome 입니다.
hit · missed 는 서버의 정지 · 고장 규칙 대조가 달고, unsure · cause 는 옛 화면에서 남긴 줄에 있습니다.
각 줄에는 occurred_at(표시가 가리키는 사건 시각) · labeled_by · labeled_at · config_ver(그 결과를
낸 판정 버전)이 함께 오고, 시각 옆의 …Stamp 는 공장 시간대(PP_TZ)로 쓴 값입니다.
키 — 두 가지 방법
| 방법 | 어디서 | 성질 |
|---|---|---|
| 발급 키 (권장) | 코파일럿 관리 콘솔 → API 키 → 발급 | 이름을 붙여 여러 개 발급하고 하나씩 폐기할 수 있습니다. 마지막 사용 시각이 남습니다 |
| 환경변수 키 | /etc/kopens/plantpulse-ai.env 의 CO_API_V1_KEY(2026.09 까지 CH_API_V1_KEY — 옛 이름은 조용히 무시됩니다) | 한 줄뿐입니다. 비워 두면 이 키로 하는 인증만 꺼집니다 |
저장은 해시로만 하므로 목록에는 앞자리만 나옵니다. 잃어버렸으면 새로 발급하고 옛 것을 폐기하십시오.
키가 주는 권한은 /api/v1 과 코파일럿 MCP(/mcp) 입니다. 2026.10 재릴리즈 6 부터 이 두 경로는 키로만 열리고,
로그인한 브라우저 세션으로 부르면 403 입니다. 키로 부른 호출의 권한 · 문서 등급은 «외부 API» 역할 묶음(기본 대외비)을 따르고
범위는 늘 전체입니다 → 권한. 관리자 경로와 API 문서
화면(/swagger-ui.html)은 관리자 로그인이 필요합니다. 도구를 «직접 부르는» 길은
열려 있지 않습니다 — 상태를 바꾸는 도구에는 사람의 승인 절차가 붙어 있기 때문입니다.
화면이 쓰는 API 중 바뀐 것 (2026.10 재릴리즈 3)
아래는 공개 API 가 아니라 코파일럿 화면이 로그인 세션으로 부르는 경로입니다. 화면을 흉내 내 부르는 연동이 있다면 확인하십시오.
| 메서드 · 경로 | 바뀐 것 |
|---|---|
PUT /api/employees/{아이디}/work-role | 🔴 관리자만 됩니다(전에는 본인도 됐습니다). 본문 {"workRole": "maintenance"}. 바꿀 때마다 감사 로그에 employee.work_role:<아이디> 로 남습니다 → 사용자 권한과 업무 역할 |
PUT /api/employees/{아이디}/home-role | 새 경로 — 본인의 시작 화면 선호. 본문 {"homeRole": "manager"}(업무 역할과 같은 다섯 값). 본인 또는 관리자가 부릅니다. 승인 권한은 바꾸지 않습니다. 메뉴 셋(묻기 · 제안 · 자동화)의 새 화면은 첫 화면이 누구에게나 묻기라 이 값을 쓰지 않습니다 — 경로는 남아 있습니다 |
GET /api/sop?query=…&assetId=… | assetId 인자가 생겼습니다 — 주면 다른 설비의 출처를 뺍니다. 응답은 {answer, sources[], citations, truncated} 이고 답은 600자 안의 문장 경계에서 자릅니다(truncated). 검색어가 너무 짧으면 400, 문서 검색 서비스가 답하지 않으면 503 + Retry-After 입니다 |
응답 형식
MCP 성공 응답
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{ ... 도구 실행 결과 JSON ... }"
}
]
}
}
MCP 오류 응답
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32600,
"message": "Invalid request",
"data": "상세 오류 메시지"
}
}
양이 많을 때 — limit 과 has_more
🔴 page · size 는 없습니다. 통합 MCP 도구는 limit 으로 상한을 주고, 응답의
has_more 로 「더 있다」를 알립니다.
{
"name": "search_domains",
"arguments": {
"query": "압축기",
"domain": "asset",
"limit": 50
}
}
| 도구 | limit 기본값 | 최대값 |
|---|---|---|
search_domains | 50 | 500 |
get_tag_history | 500 | 500 |
get_tag_values | (배열 tag_ids — 한 번에) | 100개 |
has_more: true 를 만나면쪽 번호가 없으므로 조건을 좁히는 것이 정석입니다 — domain 을 지정하거나 기간을
자르거나 검색어를 구체화하십시오. 같은 질의를 계속 밀어 다음 쪽을 받는 형태가 아닙니다.
시간 형식
통합 MCP 의 시간 파라미터는 서버 로컬 시각입니다. 이름은 from · to 이고,
형식은 YYYY-MM-DDTHH:MM:SS 입니다.
{
"name": "get_tag_history",
"arguments": {
"tag_id": "<tag_id>",
"from": "2026-03-29T00:00:00",
"to": "2026-03-29T23:59:59"
}
}
Z 를 붙이면 거부됩니다2026-03-29T00:00:00+09:00 이나 ...Z 는 받지 않습니다. 오프셋 없는 로컬 시각만
받습니다 — 서버가 지금 몇 시로 알고 있는지는 get_current_time 으로 확인하십시오.
start_time · end_time 이라는 이름도 없습니다. from · to 입니다.
응답에서 _iso 접미사가 붙은 필드는 사람이 읽기 쉬운 KST 형식입니다:
{
"timestamp": 1711641600000,
"timestamp_iso": "2026-03-29 09:00:00"
}