feat: 챗봇 신고 페이지 AI 백엔드 추가 및 기타 개선
- ai-service: 챗봇 분석/요약 엔드포인트 추가 (chatbot.py, chatbot_service.py) - tkreport: 챗봇 신고 페이지 (chat-report.html/js/css), nginx ai-api 프록시 - tkreport: 이미지 업로드 서비스 개선, M-Project 연동 신고자 정보 전달 - system1: TBM 작업보고서 UI 개선 - TKQC: 관리함/수신함 기능 개선 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
from routers import health, embeddings, classification, daily_report, rag
|
||||
from routers import health, embeddings, classification, daily_report, rag, chatbot
|
||||
from db.vector_store import vector_store
|
||||
from db.metadata_store import metadata_store
|
||||
from services.ollama_client import ollama_client
|
||||
@@ -64,6 +64,7 @@ app.include_router(embeddings.router, prefix="/api/ai")
|
||||
app.include_router(classification.router, prefix="/api/ai")
|
||||
app.include_router(daily_report.router, prefix="/api/ai")
|
||||
app.include_router(rag.router, prefix="/api/ai")
|
||||
app.include_router(chatbot.router, prefix="/api/ai")
|
||||
|
||||
|
||||
@app.get("/")
|
||||
|
||||
37
ai-service/routers/chatbot.py
Normal file
37
ai-service/routers/chatbot.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from services.chatbot_service import analyze_user_input, summarize_report
|
||||
|
||||
router = APIRouter(tags=["chatbot"])
|
||||
|
||||
|
||||
class AnalyzeRequest(BaseModel):
|
||||
user_text: str
|
||||
categories: dict = {}
|
||||
|
||||
|
||||
class SummarizeRequest(BaseModel):
|
||||
description: str = ""
|
||||
type: str = ""
|
||||
category: str = ""
|
||||
item: str = ""
|
||||
location: str = ""
|
||||
project: str = ""
|
||||
|
||||
|
||||
@router.post("/chatbot/analyze")
|
||||
async def chatbot_analyze(req: AnalyzeRequest):
|
||||
try:
|
||||
result = await analyze_user_input(req.user_text, req.categories)
|
||||
return {"success": True, **result}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="AI 분석 중 오류가 발생했습니다")
|
||||
|
||||
|
||||
@router.post("/chatbot/summarize")
|
||||
async def chatbot_summarize(req: SummarizeRequest):
|
||||
try:
|
||||
result = await summarize_report(req.model_dump())
|
||||
return {"success": True, **result}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail="AI 요약 중 오류가 발생했습니다")
|
||||
110
ai-service/services/chatbot_service.py
Normal file
110
ai-service/services/chatbot_service.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import json
|
||||
from services.ollama_client import ollama_client
|
||||
|
||||
|
||||
ANALYZE_SYSTEM_PROMPT = """당신은 공장 현장 신고 접수를 도와주는 AI 도우미입니다.
|
||||
사용자가 현장에서 발견한 문제를 설명하면, 아래 카테고리 목록을 참고하여 가장 적합한 신고 유형과 카테고리를 제안해야 합니다.
|
||||
|
||||
신고 유형:
|
||||
- nonconformity (부적합): 제품/작업 품질 관련 문제
|
||||
- facility (시설설비): 시설, 설비, 장비 관련 문제
|
||||
- safety (안전): 안전 위험, 위험 요소 관련 문제
|
||||
|
||||
반드시 아래 JSON 형식으로만 응답하세요. 다른 텍스트는 포함하지 마세요:
|
||||
{
|
||||
"organized_description": "정리된 설명 (1-2문장)",
|
||||
"suggested_type": "nonconformity 또는 facility 또는 safety",
|
||||
"suggested_category_id": 카테고리ID(숫자) 또는 null,
|
||||
"confidence": 0.0~1.0 사이의 확신도
|
||||
}"""
|
||||
|
||||
SUMMARIZE_SYSTEM_PROMPT = """당신은 공장 현장 신고 내용을 요약하는 AI 도우미입니다.
|
||||
주어진 신고 정보를 보기 좋게 정리하여 한국어로 요약해주세요.
|
||||
|
||||
반드시 아래 JSON 형식으로만 응답하세요:
|
||||
{
|
||||
"summary": "요약 텍스트"
|
||||
}"""
|
||||
|
||||
|
||||
async def analyze_user_input(user_text: str, categories: dict) -> dict:
|
||||
"""사용자 초기 입력을 분석하여 유형 제안 + 설명 정리"""
|
||||
category_context = ""
|
||||
for type_key, cats in categories.items():
|
||||
type_label = {"nonconformity": "부적합", "facility": "시설설비", "safety": "안전"}.get(type_key, type_key)
|
||||
cat_names = [f" - ID {c['id']}: {c['name']}" for c in cats]
|
||||
category_context += f"\n[{type_label} ({type_key})]\n" + "\n".join(cat_names) + "\n"
|
||||
|
||||
prompt = f"""카테고리 목록:
|
||||
{category_context}
|
||||
|
||||
사용자 입력: "{user_text}"
|
||||
|
||||
위 카테고리 목록을 참고하여 JSON으로 응답하세요."""
|
||||
|
||||
raw = await ollama_client.generate_text(prompt, system=ANALYZE_SYSTEM_PROMPT)
|
||||
|
||||
try:
|
||||
start = raw.find("{")
|
||||
end = raw.rfind("}") + 1
|
||||
if start >= 0 and end > start:
|
||||
result = json.loads(raw[start:end])
|
||||
# Validate required fields
|
||||
if "organized_description" not in result:
|
||||
result["organized_description"] = user_text
|
||||
if "suggested_type" not in result:
|
||||
result["suggested_type"] = "nonconformity"
|
||||
if "confidence" not in result:
|
||||
result["confidence"] = 0.5
|
||||
return result
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return {
|
||||
"organized_description": user_text,
|
||||
"suggested_type": "nonconformity",
|
||||
"suggested_category_id": None,
|
||||
"confidence": 0.3,
|
||||
}
|
||||
|
||||
|
||||
async def summarize_report(data: dict) -> dict:
|
||||
"""최종 신고 내용을 요약"""
|
||||
prompt = f"""신고 정보:
|
||||
- 설명: {data.get('description', '')}
|
||||
- 유형: {data.get('type', '')}
|
||||
- 카테고리: {data.get('category', '')}
|
||||
- 항목: {data.get('item', '')}
|
||||
- 위치: {data.get('location', '')}
|
||||
- 프로젝트: {data.get('project', '')}
|
||||
|
||||
위 정보를 보기 좋게 요약하여 JSON으로 응답하세요."""
|
||||
|
||||
raw = await ollama_client.generate_text(prompt, system=SUMMARIZE_SYSTEM_PROMPT)
|
||||
|
||||
try:
|
||||
start = raw.find("{")
|
||||
end = raw.rfind("}") + 1
|
||||
if start >= 0 and end > start:
|
||||
result = json.loads(raw[start:end])
|
||||
if "summary" in result:
|
||||
return result
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
# Fallback: construct summary manually
|
||||
parts = []
|
||||
if data.get("type"):
|
||||
parts.append(f"[{data['type']}]")
|
||||
if data.get("category"):
|
||||
parts.append(data["category"])
|
||||
if data.get("item"):
|
||||
parts.append(f"- {data['item']}")
|
||||
if data.get("location"):
|
||||
parts.append(f"\n위치: {data['location']}")
|
||||
if data.get("project"):
|
||||
parts.append(f"\n프로젝트: {data['project']}")
|
||||
if data.get("description"):
|
||||
parts.append(f"\n내용: {data['description']}")
|
||||
|
||||
return {"summary": " ".join(parts) if parts else "신고 내용 요약"}
|
||||
Reference in New Issue
Block a user