Implement Phase 12 feedback, Phase 13 Semantic Chunker, Phase 13-B Reranker, Bug 5 thinking fix
- Phase 12: FeedbackRepository + td_feedback 테이블, Gradio 👍/👎 이벤트, run_id 추적, LangSmith create_feedback() 연동 - Phase 13: 커스텀 _SemanticSplitter 제거 → langchain_experimental.SemanticChunker 교체, buffer_size/threshold_type 환경변수 적용 - Phase 13-B: RerankService (Cross-Encoder), RetrieverService.search()에 reranker 통합, tools.py as_retriever() → search() 전환 - Bug 5: mlx_chat_model enable_thinking 런타임 오버라이드, agent_service stream_mode=["messages","custom"] 이중 스트림, thinking 토큰 custom 이벤트로 emit - ROADMAP: LLM 모델명 8B 반영, RAG에 Reranker 추가, 추천 진행 순서 갱신 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Gradio Web UI — 율봇 Phase 4 + Phase 9/10 + Phase 14(음성)."""
|
||||
"""Gradio Web UI — 율봇 Phase 4 + Phase 9/10 + Phase 12(피드백) + Phase 14(음성)."""
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
@@ -17,6 +17,7 @@ db.init_schema()
|
||||
|
||||
ingestion = container.ingestion_service()
|
||||
retriever = container.retriever_service()
|
||||
feedback_repo = container.feedback_repository()
|
||||
|
||||
_cfg = container.config()
|
||||
_agent_cache: dict[str, AgentService] = {}
|
||||
@@ -44,7 +45,7 @@ def transcribe_audio(filepath: str) -> str:
|
||||
|
||||
|
||||
def tts_speak(text: str, voice: str) -> str | None:
|
||||
"""텍스트를 macOS say 명령어로 음성 변환, 재생용 wav 파일 경로 반환."""
|
||||
"""텍스트를 macOS say 명령어로 음성 변환, 재생용 aiff 파일 경로 반환."""
|
||||
if not text:
|
||||
return None
|
||||
try:
|
||||
@@ -77,36 +78,72 @@ def _get_agent(user_id: str) -> AgentService:
|
||||
return _agent_cache[user_id]
|
||||
|
||||
|
||||
async def respond(message, history, show_thinking, user_id, use_tts):
|
||||
async def respond(message, history, show_thinking, user_id, use_tts, run_ids):
|
||||
if not message.strip():
|
||||
yield history, "", None
|
||||
yield history, "", None, run_ids
|
||||
return
|
||||
|
||||
agent = _get_agent(user_id)
|
||||
history = list(history)
|
||||
run_ids = list(run_ids)
|
||||
history.append({"role": "user", "content": message})
|
||||
history.append({"role": "assistant", "content": ""})
|
||||
yield history, "", None
|
||||
yield history, "", None, run_ids
|
||||
|
||||
async for token in agent.stream_response(message, show_thinking=show_thinking):
|
||||
history[-1]["content"] += token
|
||||
yield history, "", None
|
||||
yield history, "", None, run_ids
|
||||
|
||||
run_ids.append(agent.last_run_id)
|
||||
|
||||
if use_tts:
|
||||
response_text = history[-1]["content"]
|
||||
audio_path = tts_speak(response_text, _cfg.tts_voice)
|
||||
yield history, "", audio_path
|
||||
yield history, "", audio_path, run_ids
|
||||
else:
|
||||
yield history, "", None, run_ids
|
||||
|
||||
|
||||
def handle_feedback(like_data: gr.LikeData, history, run_ids, user_id):
|
||||
idx = like_data.index
|
||||
if isinstance(idx, (list, tuple)):
|
||||
idx = idx[0]
|
||||
if not isinstance(idx, int) or idx >= len(history):
|
||||
return
|
||||
if history[idx].get("role") != "assistant":
|
||||
return
|
||||
asst_turn = sum(1 for m in history[:idx] if m.get("role") == "assistant")
|
||||
run_id = run_ids[asst_turn] if asst_turn < len(run_ids) else None
|
||||
|
||||
def _to_str(val) -> str:
|
||||
return val if isinstance(val, str) else str(val)
|
||||
|
||||
user_msg = _to_str(history[idx - 1]["content"]) if idx > 0 else ""
|
||||
asst_msg = _to_str(history[idx]["content"])
|
||||
rating = 1 if like_data.liked else -1
|
||||
|
||||
try:
|
||||
feedback_repo.save_feedback(user_id, user_msg, asst_msg, rating, run_id)
|
||||
except Exception as e:
|
||||
print(f"[Feedback] DB 저장 실패: {e}")
|
||||
|
||||
if run_id and os.getenv("LANGCHAIN_TRACING_V2") == "true":
|
||||
try:
|
||||
from langsmith import Client
|
||||
Client().create_feedback(run_id=run_id, key="user_feedback", score=rating)
|
||||
except Exception as e:
|
||||
print(f"[Feedback] LangSmith 기록 실패: {e}")
|
||||
|
||||
|
||||
def switch_user(user_id):
|
||||
"""사용자 전환 시 채팅 화면만 초기화 (대화 이력은 유지)."""
|
||||
return []
|
||||
"""사용자 전환 시 채팅 화면과 run_ids 초기화 (대화 이력은 DB에 유지)."""
|
||||
return [], []
|
||||
|
||||
|
||||
def reset_chat(user_id):
|
||||
agent = _get_agent(user_id)
|
||||
agent.reset()
|
||||
return []
|
||||
return [], []
|
||||
|
||||
|
||||
def ingest_files(files):
|
||||
@@ -143,6 +180,7 @@ with gr.Blocks(title="율봇") as demo:
|
||||
gr.Markdown("# 율봇\n육아·금융 전문 AI 상담 도우미")
|
||||
|
||||
user_state = gr.State(DEFAULT_USER)
|
||||
run_ids_state = gr.State([])
|
||||
|
||||
with gr.Tab("대화"):
|
||||
with gr.Row():
|
||||
@@ -185,7 +223,7 @@ with gr.Blocks(title="율봇") as demo:
|
||||
user_selector.change(
|
||||
switch_user,
|
||||
inputs=[user_selector],
|
||||
outputs=[chatbot],
|
||||
outputs=[chatbot, run_ids_state],
|
||||
).then(
|
||||
lambda u: u, inputs=[user_selector], outputs=[user_state]
|
||||
)
|
||||
@@ -198,15 +236,21 @@ with gr.Blocks(title="율봇") as demo:
|
||||
|
||||
send_btn.click(
|
||||
respond,
|
||||
inputs=[msg_box, chatbot, show_thinking, user_state, use_tts],
|
||||
outputs=[chatbot, msg_box, tts_output],
|
||||
inputs=[msg_box, chatbot, show_thinking, user_state, use_tts, run_ids_state],
|
||||
outputs=[chatbot, msg_box, tts_output, run_ids_state],
|
||||
)
|
||||
msg_box.submit(
|
||||
respond,
|
||||
inputs=[msg_box, chatbot, show_thinking, user_state, use_tts],
|
||||
outputs=[chatbot, msg_box, tts_output],
|
||||
inputs=[msg_box, chatbot, show_thinking, user_state, use_tts, run_ids_state],
|
||||
outputs=[chatbot, msg_box, tts_output, run_ids_state],
|
||||
)
|
||||
reset_btn.click(reset_chat, inputs=[user_state], outputs=[chatbot, run_ids_state])
|
||||
|
||||
chatbot.like(
|
||||
handle_feedback,
|
||||
inputs=[chatbot, run_ids_state, user_state],
|
||||
outputs=[],
|
||||
)
|
||||
reset_btn.click(reset_chat, inputs=[user_state], outputs=[chatbot])
|
||||
|
||||
with gr.Tab("문서 등록"):
|
||||
gr.Markdown("PDF 또는 TXT 파일을 업로드하면 율봇이 내용을 참고해 답변합니다.")
|
||||
|
||||
Reference in New Issue
Block a user