86 lines
2.8 KiB
Python
86 lines
2.8 KiB
Python
# pxy_langchain/api/views.py
|
||
import json
|
||
import logging
|
||
from typing import Optional
|
||
|
||
from django.http import JsonResponse, HttpResponse
|
||
from django.views.decorators.csrf import csrf_exempt
|
||
|
||
from pxy_langchain.models import AIAssistant
|
||
from pxy_langchain.services import LangchainAIService
|
||
|
||
# We read the bot->assistant mapping from pxy_bots, so bots can decide which assistant to use.
|
||
from pxy_bots.models import TelegramBot
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def _get_assistant_for_req(req: dict) -> Optional[AIAssistant]:
|
||
"""
|
||
Resolve which AIAssistant to use:
|
||
1) Try via TelegramBot (req['bot']['username'] matches TelegramBot.name or .username).
|
||
2) Fallback: optional req['context']['assistant_name'].
|
||
"""
|
||
bot_username = (((req.get("bot") or {}).get("username")) or "").strip()
|
||
if bot_username:
|
||
# Try Bot.name first (that’s how your req.v1 is built), then .username
|
||
bot = (TelegramBot.objects.filter(name=bot_username).first() or
|
||
TelegramBot.objects.filter(username=bot_username).first())
|
||
if bot and bot.assistant_id:
|
||
return bot.assistant
|
||
|
||
# Fallback: explicit assistant name (optional)
|
||
ctx = req.get("context") or {}
|
||
assistant_name = (ctx.get("assistant_name") or "").strip()
|
||
if assistant_name:
|
||
return AIAssistant.objects.filter(name=assistant_name).first()
|
||
|
||
return None
|
||
|
||
|
||
@csrf_exempt
|
||
def chat(request):
|
||
"""
|
||
POST /api/langchain/chat
|
||
Body: req.v1 (canonical envelope produced by pxy_bots)
|
||
Returns: render.v1 (text message with the LLM answer)
|
||
"""
|
||
if request.method != "POST":
|
||
return HttpResponse(status=405)
|
||
|
||
try:
|
||
env = json.loads(request.body.decode("utf-8") or "{}")
|
||
except Exception:
|
||
return JsonResponse({"ok": False, "error": "invalid_json"}, status=400)
|
||
|
||
assistant = _get_assistant_for_req(env)
|
||
if not assistant:
|
||
return JsonResponse({"ok": False, "error": "assistant_not_found"}, status=400)
|
||
|
||
# Pull user text (or caption) from req.v1
|
||
inp = env.get("input") or {}
|
||
user_text = (inp.get("text") or inp.get("caption") or "").strip()
|
||
|
||
# If nothing to say, keep it explicit
|
||
if not user_text:
|
||
return JsonResponse({
|
||
"schema_version": "render.v1",
|
||
"messages": [{"type": "text", "text": "No text received."}]
|
||
})
|
||
|
||
try:
|
||
svc = LangchainAIService(assistant)
|
||
answer = svc.generate_response(user_text) # synchronous call
|
||
except Exception as e:
|
||
logger.exception("langchain.chat.error")
|
||
return JsonResponse({"ok": False, "error": f"llm_error:{e.__class__.__name__}"}, status=500)
|
||
|
||
# Minimal render.v1
|
||
spec = {
|
||
"schema_version": "render.v1",
|
||
"messages": [
|
||
{"type": "text", "text": str(answer)}
|
||
]
|
||
}
|
||
return JsonResponse(spec)
|