매장 시스템의 장애 알림을 받았을 때, 담당자는 모니터링 정보뿐만 아니라 시스템 대장이나 운영 절차서도 확인합니다.
여기서 필요한 것은 '가장 그럴듯한 복구책'이 아니라, 무엇을 알고 있고 무엇이 미확인인지 구분한 초기 대응 보고서입니다.
가상의 소매 기업 '토토마트(東都マート)'를 가정하고, OCI Generative AI의 Enterprise AI 기능을 사용하여 웹 애플리케이션을 만들었습니다.
Python으로 알림과 대장을 완전 일치 조회하고, Responses API의 file_search로 운영 절차서를 검색합니다.
입력과 답변에는 OCI Generative AI Guardrails를 적용합니다.
이 기사의 매장, 알림, 연락처는 모두 가상의 정보입니다.
OCI의 OCID, 벡터 스토어 ID, 레지스트리 이름, 연결 대상 등은 플레이스홀더로 대체했습니다.
토토마트는 여러 매장의 POS 단말기와 각 매장에서 사용하는 공통 시스템을 운영하는 소매 기업이라는 설정입니다.
본부의 시스템 운영 담당자가 장애 알림을 받고, 매장에 미치는 영향 확인과 관계 부서에 대한 1차 연락을 수행하는 업무를 상정했습니다.
아래는 이 데모에서 설정한 가상의 업무 과제입니다.
| 가정하는 과제 | 초기 대응 지원 Agent가 할 일 | 업무상 기대할 수 있는 효과 |
|---|
| 알림, 시스템 대장, 운영 절차서가 분산되어 있어 대상 시스템이나 담당 부서를 조사하는 데 시간이 걸림 | 알림 ID로부터 JSON과 CSV를 완전 일치로 조회하고, 관련 절차서를 벡터 스토어에서 검색함 | 여러 자료를 찾아 비교해야 하는 부담을 줄이고, 확인에 착수하기 쉽게 함 |
| ... | | |
예를 들어, 공통 DB의 연결 실패가 32개 매장에서 18분 동안 관측된 경우, 담당자는 '어떤 시스템인지', '어떤 절차를 적용할지', '누구에게 연락할지'를 순서대로 조사해야 합니다.
이 Agent는 알림과 대장의 조회 결과에 절차서의 조건을 맞춰 P1 후보로 삼을 근거, 판매 중단 여부는 미확인이라는 점, 확인 항목 및 연락처를 하나의 초안으로 정리합니다.
담당자는 그 초안을 확인하여 매장에 대한 업무 영향 확인이나 관계 부서에 대한 연락을 진행합니다.
여기서 목표하는 것은 자료를 찾아서 초기 정보를 정리하는 부담을 줄이고, 사람이 판단하고 연락하기 위한 재료를 모으는 것입니다.
대응 시간 단축이나 판단의 편차 감소는 기대 효과이며, 이 데모에서 정량적으로 측정한 결과는 아닙니다.
기사에서 소개하는 구현과 가상 자료는 GitHub에 공개했습니다.
git clone https://github.com/nomukyounomukyou-gh/qiita.git
cd qiita/pos-incident-agent
ZIP을 다운로드하여 전개해도 같은 파일을 얻을 수 있습니다.
공개물에는 실제 환경의 연결 설정이나 키는 포함되어 있지 않습니다.
settings.example.json
를 settings.json
으로 복사하고, 직접 만든 OCI 프로젝트・벡터 스토어・모델・Guardrails 버전을 설정해 주세요.
| 공개 파일 | 역할 |
|---|
pos_incident_agent.py | Responses API, 함수 도구(function tool), 입력 및 답변의 Guardrails |
guardrails.py | ApplyGuardrails 호출과 보류/통지 판정 |
web_app.py , chat.html | 채팅 화면, 세션 기록, 헬스 체크 |
oci_vector_store_upload.py | 검색 자료 등록, 인덱싱 완료 확인, 검색 확인 |
settings.example.json | 개별 환경 값이 없는 설정 템플릿 |
alerts.json , system_inventory.csv | 함수 도구가 조회하는 가상 데이터 |
pos_incident_runbook.md , *_for_rag.md | 벡터 스토어에 넣을 가상 자료 |
demo_questions.md | 일반적인 장애 대응과 Guardrails의 데모 질문 |
Dockerfile , requirements.txt | 컨테이너와 의존 관계 |
.gitignore , .dockerignore | 로컬 설정 및 불필요 파일 제외 |
test_guarded_agent.py , test_vector_store_upload.py | 입력/답변 보류 및 자료 등록 등의 회귀 테스트 |
README.md | OCI 준비부터 웹 배포까지의 재현 절차 |
자료 등록과 CLI 실행은 IAM 권한을 설정한 OCI Compute 위에서 Instance Principal를 사용하는 구성입니다.
앱은 Hosted Application의 Resource Principal로 작동합니다.
사용자의 OCI 리소스와 인증 및 권한 준비가 필요하며, 배포(Deployment) 생성은 README에 따라 OCI 콘솔에서 진행합니다.
브라우저에서 알림 ID를 포함한 질문을 보내면, Agent는 다음 내용을 정리합니다.
- 대상 시스템과 모니터링 상의 영향 범위
- 절차서(runbook) 기반의 잠정 우선순위와 그 근거
- 아직 확인되지 않은 업무 영향
- 1차 연락처와 에스컬레이션(escalation) 대상 초안
예를 들어, 공통 DB 연결 실패를 32개 매장에서 18분간 관측한 경우, 절차서의 조건에 비추어 'P1 후보'로 판단합니다.
다만, 32개 매장에서 판매가 중단되었다고 단정하지는 않습니다.
이는 모니터링에서 이상을 관측한 매장 수이며, 회계나 결제가 실제로 멈춘 매장 수는 별도로 확인이 필요합니다.
이 다이어그램은 알림과 장부를 JSON/CSV로 보유하는 초기 버전의 구성입니다.
함수 호출 요청(function call request)은 모델로부터 반환되지만, lookup_alert와 lookup_system을 실행하는 것은 Hosted Application 내부의 Python 코드입니다.
file_search는 OCI 측의 벡터 스토어를 검색합니다.
Guardrails는 Responses API와 별도로 호출되어 질문을 Agent에게 전달하기 전과 최종 답변을 표시하기 전에 판정합니다.
검색용 Markdown의 벡터 스토어 등록은 사전 준비 과정입니다.
다이어그램의 점선 화살표로 표시된 처리는 질문이 있을 때마다 실행되는 것은 아닙니다.
OCI Responses API는 OpenAI와 호환되는 요청 형식을 사용할 수 있지만, 인증, 엔드포인트, 리소스 관리는 OCI 측에서 담당합니다.
OCI 공식 문서에서도 file_search와 function이 지원 대상으로 안내되고 있습니다.
OCI Responses API
처음에 Codex를 사용하여 다음 3가지 종류의 가상 데이터를 만들었습니다.
| 파일 | 내용 | Agent에서의 처리 |
|---|
alerts.json | 알림 ID, 시스템 ID, 관측 매장 수, 지속 시간 등 | lookup_alert로 완전 일치 조회 |
system_inventory.csv | 시스템 ID, 담당 부서, 연락처, 절차서 ID 등 | lookup_system으로 완전 일치 조회 |
pos_incident_runbook.md | P1~P3의 판정 조건, 초동 확인 항목, 연락 규칙 | 벡터 스토어에서 file_search |
알림은 다음과 같은 형태입니다.
실제 데이터가 아닌, 2026년 10월 8일 11:15 시점을 가정한 스냅샷입니다.
{
"alert_id": "ALT-20261008-001",
"system_id": "POS-CORE-DB",
...
}
절차서의 판정 조건도 모호한 문장으로 두지 않고, 검증하기 쉬운 형태로 만들었습니다.
| 임시 우선순위 | 절차서 조건 예시 |
|---|
| P1 후보 | POS 공통 기반에서 10개 매장 이상의 이상을 관측하고, 15분 이상 지속 |
| ... | |
감시 레벨의 critical | |
그리고 업무상의 P1은 별개의 개념입니다.
우선도는 duration_minutes, affected_store_count_observed, 장부상의 대상 구분으로부터 판정하게 합니다.
벡터 스토어에는 절차서 외에, 장부와 알람을 검색용 Markdown으로 변환한 것도 등록했습니다.
검증 시 CSV를 그대로 벡터 스토어에 등록하면 unsupported_file이 반환되었기 때문입니다.
반면, ID나 수치의 정확한 조회에는 원래의 JSON/CSV를 함수 툴로 사용합니다.
RAG만으로 수치 판정을 맡기지 않은 구성입니다.
파일 등록은 Files API에 업로드와, 벡터 스토어와의 연관付け의 2단계입니다.
생성한 벡터 스토어는 「Unstructured data」를 선택했습니다.
벡터 스토어 생성 / 파일 추가 및 검색
작성된 벡터 스토어에 자료를 등록하는 스크립트는 다음과 같이 실행했습니다.
변수에는 각자의 값을 넣어주세요.
REGION="<region>"
PROJECT_OCID="<project-ocid>"
COMPARTMENT_OCID="<compartment-ocid>"
...
이 스크립트는 OCI Compute의 Instance Principal로 실행했습니다.
등록 후에는 인덱스 상태가 completed가 된 것과, 검색 API에서 절차서 P1 조건이 반환되는 것을 확인했습니다.
데모 질문집은 기대 답변을 포함하고 있기 때문에, 검색 대상에는 넣지 않았습니다.
설정 파일은 ID만 보관하고, 비밀키나 Auth Token은 컨테이너에 넣지 않습니다.
아래는 공개용으로 값을 대체한 예시입니다.
{
"region": "<region>",
"project_id": "<project-ocid>",
...
Hosted Application 내부에서는 Resource Principal, 빌드용 OCI Compute 상에서는 Instance Principal을 사용합니다.
Responses API에는 OCI 서명된 HTTP 요청을 보냅니다.
session = requests.Session()
if os.environ.get("OCI_RESOURCE_PRINCIPAL_VERSION"):
session.auth = oci.auth.signers.get_resource_principals_signer()
...
툴은 file_search와 2개의 함수를 전달합니다.
lookup_alert와 lookup_system은 ID의 완전 일치만을 허용하며, 장부에 없는 ID는 유사한 레코드로 보완하지 않습니다.
available_tools = [
{"type": "file_search", "vector_store_ids": [settings["vector_store_id"]]},
{
...
모델로부터 function_call이 반환되면, Python 측에서 JSON/CSV를 조회하고, call_id가 붙은 function_call_output을 다음 Responses API 호출에 전달합니다.
이 반복을 통해 검색 결과와 장부 정보를 합친 답변을 얻습니다.
OpenAI 공식 문서의 Function Calling 흐름
for item in response["output"]:
if item["type"] == "function_call":
result = execute_call(item["name"], item["arguments"], alerts, systems)
...
시스템 지시에는 「감시로 관측한 매장 수를 판매 정지 매장 수와 동일시하지 않는다」「절차서에 없는 임계치를 만들지 않는다」「복구 조치는 제안/실행하지 않는다」라고 명기했습니다.
출력은 어디까지나 담당자가 확인할 초안입니다.
이 앱은 ApplyGuardrails를 입력 전과 답변 후에 호출합니다.
입력에서 콘텐츠 모더레이션 또는 프롬프트 인젝션이 감지된 경우에는, Responses API를 호출하지 않고 보류합니다.
답변에서 콘텐츠 모더레이션이 감지된 경우에는 화면에 답변을 표시하지 않습니다.
input_check = evaluate(question, settings, "input")
if input_check["blocked"]:
return {"answer": "", "trace": [], "checks": [input_check]}
...
PII 감지는 이 데모에서는 화면에 통지하고 처리를 계속합니다.
가상의 연락처 이메일 주소를 포함하는 일반적인 답변까지 막지 않기 위한 설정입니다.
PII 감지 대응 언어는 영어만 가능하므로, 일본어 개인정보 보호를 이것에만 맡기는 설계는 하지 않았습니다.
또한, Guardrails API가 실패할 경우에는 Agent 처리를 계속하지 않고 오류로 처리합니다.
사용 가능한 Guardrails 버전을 확인한 후 설정값을 고정했습니다.
OCI Generative AI Guardrails
여기서 체크하는 것은 사용자의 입력문과 최종 답변입니다.
벡터 스토어에서 가져온 개별 문서 조각을 별도로 검사하는 구현은 아닙니다.
검색 자료 자체의 관리도 필요합니다.
웹 화면은 Python의 HTTP 서버로 구현하여, GET /
으로 채팅 화면을, POST /
으로 질문을 받습니다.
또한, GET /health
과 GET /ready
도 준비했습니다.
화면에는 샘플 질문, 대화, 참조 도구, 입력 및 답변 각각의 Guardrails 판정 결과를 표시합니다.
Hosted Deployment용 컨테이너는 linux/amd64
로 빌드하고, 0.0.0.0:8080
에서 대기합니다.
OCI 공식 문서에 있는 헬스 체크 요구사항에 맞추었습니다.
컨테이너 이미지 준비
FROM docker.io/library/python:3.12-slim
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1 HOME=/tmp
WORKDIR /app
...
OCI Container Registry(OCIR)에는 레지스트리의 호스트명, 네임스페이스, 리포지토리명을 각자의 값으로 대체하여 Push합니다.
인증에는 OCIR용 Auth Token을 사용하고, 스크립트나 이미지에 내장하지 않도록 했습니다.
REGISTRY="<your-region-ocir-host>"
NAMESPACE="<your-tenancy-namespace>"
REPOSITORY="<your-repository>"
...
OCI Generative AI의 Application과 Hosted Deployment를 만들고, OCIR 리포지토리와 태그를 선택하여 활성화합니다.
기존 Deployment를 업데이트하는 경우에는 새로운 이미지 태그를 아티팩트로 추가하고 활성화합니다.
앱의 인증 방식과 IAM 권한은 이용 환경에 맞춰 설정해 주십시오.
Deployment 생성 / 아티팩트 추가
검증 시에는 다음 질문들을 준비했습니다.
모두 가상의 알람입니다.
| 질문 | 기대하는 동작 |
|---|
ALT-20261008-001의 초기 대응 양식 | 공통 DB, 32개 매장에서 이상 관측, 18분 지속 후 P1 후보. 판매 중단은 미확인으로 명기 |
ALT-20261008-002의 우선순위 | 대장 등록된 단일 단말, 6분 지속 후 P3 후보 |
ALT-20261008-003의 우선순위 | 대상 시스템이 대장에 없으므로 미판정. 유사 ID에서 담당자를 추측하지 않음 |
| 지시값 덮어쓰기를 요구하는 입력 | 프롬프트 인젝션으로 입력을 보류 |
첫 번째 답변에서는 lookup_alert,
lookup_system,
file_search가 참조 도구로 표시되었고, P1 후보의 근거와 미확인 사항이 나왔습니다.
프롬프트 인젝션용 질문도 검증 시점의 Guardrails 버전으로 보류되었습니다.
감지 결과는 버전에 따라 문구도 달라지므로, 데모 전에 같은 입력으로 재확인합니다.
일반 질문에서 표시할 내용의 요약 예시입니다.
문구는 모델 실행마다 바뀝니다.
대상 시스템: POS-CORE-DB (POS 공통 DB)
감시상의 영향: 40개 매장 중 32개 매장에서 연결 실패를 관측. 판매 중단 매장 수는 미확인.
임시 우선순위: P1 후보
...
↓ 초기 화면
↓ 질문 1
↓ 질문 2
↓ 질문 3
↓ 질문 4
↓ 질문 5
↓ 질문 6
↓ 질문 7
이번에는 알림(アラート)과 장부(台帳)를 파일의 고정 스냅샷 형태로 컨테이너에 넣었습니다.
실제 운영 환경에서는 모니터링 기반이나 구성 관리 시스템과 연동하여 최신성(鮮度)과 변경 이력을 보장해야 합니다.
또한, 화면의 대화 표시는 브라우저의 sessionStorage를 사용하고,
Agent에 전달하는 가장 최근의 대화 문맥은 컨테이너 내부 메모리입니다.
데모에는 충분하지만, 앱 재시작이나 여러 복제본(replica)에서는 대화 문맥을 공유할 수 없습니다.
지속적인 이용을 위해서는 영구 저장소(永続化先)를 설계해야 합니다.
초동 대응 양식은 사람이 확인하는 초안에 그치게 하고, 연락, 기표(起票), DB나 단말기의 재시작은 자동 실행하지 않습니다.
이 경계를 명확히 하여 현장에서 업무 적용을 검토할 수 있는 데모를 만들었습니다.