148 lines
4.9 KiB
Python
148 lines
4.9 KiB
Python
import logging
|
|
import threading
|
|
import time
|
|
from typing import Literal, Dict, Any
|
|
import config
|
|
from dto.requests import GameStartRequestDTO, GameDecisionRequestDTO
|
|
from services.extractor import extract_profile, extract_passport, extract_description, extract_account
|
|
from services.julius_baer_api_client import JuliusBaerApiClient
|
|
from utils.storage.game_files_manager import store_game_round_data
|
|
from langchain_google_genai import ChatGoogleGenerativeAI
|
|
from validation.llm_validate import AdvisorDecision
|
|
from langchain_core.prompts import ChatPromptTemplate
|
|
from langchain_core.output_parsers import PydanticOutputParser
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class Advisor:
|
|
|
|
def __init__(self):
|
|
self.client = JuliusBaerApiClient()
|
|
self._thread = None
|
|
|
|
def start(self):
|
|
self.play()
|
|
|
|
def play_on_separate_thread(self):
|
|
if self._thread and self._thread.is_alive():
|
|
log.warning('Game loop already running.')
|
|
return self._thread
|
|
|
|
self._thread = threading.Thread(target=self.play, daemon=True)
|
|
self._thread.start()
|
|
return self._thread
|
|
|
|
def play(self):
|
|
log.info('playing')
|
|
payload = GameStartRequestDTO(player_name=config.API_TEAM)
|
|
start_response = self.client.start_game(payload)
|
|
log.info('game started, session id: %s', start_response.session_id)
|
|
|
|
client_id = start_response.client_id
|
|
decision = self.make_decision(start_response.client_data)
|
|
|
|
decision_counter = 0
|
|
|
|
is_game_running = True
|
|
while is_game_running:
|
|
payload = GameDecisionRequestDTO(
|
|
decision=decision.answer,
|
|
session_id=start_response.session_id,
|
|
client_id=client_id,
|
|
)
|
|
|
|
log.info('client id: %s', client_id)
|
|
decision_response = self.client.send_decision(payload)
|
|
log.info(f'decision: {decision}, response status: {decision_response.status}, score: {decision_response.score}')
|
|
|
|
status = decision_response.status
|
|
is_game_running = status not in ['gameover', 'complete']
|
|
client_id = decision_response.client_id
|
|
|
|
decision = self.make_decision(decision_response.client_data)
|
|
|
|
# Handle first response from game initialization logic
|
|
if decision_counter == 0:
|
|
# Store start response
|
|
store_game_round_data(decision.answer, start_response, decision_counter, str(start_response.session_id) ,status)
|
|
else:
|
|
# store ongoing decision response
|
|
store_game_round_data(decision.answer, decision_response, decision_counter, str(start_response.session_id), status)
|
|
|
|
decision_counter += 1
|
|
time.sleep(1)
|
|
|
|
def make_decision(self, client_data: Dict[str, Any]) -> AdvisorDecision:
|
|
# 1. Extraction des données
|
|
profile = extract_profile(client_data)
|
|
passport = extract_passport(client_data)
|
|
description = extract_description(client_data)
|
|
account = extract_account(client_data)
|
|
|
|
# 2. Création du parser
|
|
parser = PydanticOutputParser(pydantic_object=AdvisorDecision)
|
|
format_instructions = parser.get_format_instructions()
|
|
|
|
# 3. Prompt enrichi
|
|
prompt = ChatPromptTemplate.from_template(
|
|
"""You are a compliance analyst in a private bank.
|
|
You are given structured data extracted from four different documents of a new client application.
|
|
|
|
Your task is to accept or reject the client's application for private banking.
|
|
|
|
Only reject the application if there is an inconsistency in the data provided.
|
|
Inconsistencies include:
|
|
- Incorrect data (e.g., mismatched or invalid information)
|
|
- Incomplete data (e.g., missing required fields)
|
|
- Implausible or suspicious details
|
|
|
|
Use the extracted profile, description, and account details to cross-check the information in the passport and other documents.
|
|
|
|
Be highly critical. Reject if there's any doubt or if anything feels wrong.
|
|
|
|
Return only JSON matching this format:
|
|
{format_instructions}
|
|
|
|
---
|
|
|
|
**Document: Passport**
|
|
{passport}
|
|
|
|
---
|
|
|
|
**Document: Profile**
|
|
{profile}
|
|
|
|
---
|
|
|
|
**Document: Description**
|
|
{description}
|
|
|
|
---
|
|
|
|
**Document: Account**
|
|
{account}"""
|
|
)
|
|
|
|
# 4. Chaîne LLM
|
|
chain = prompt | ChatGoogleGenerativeAI(model="gemini-2.0-flash") | parser
|
|
|
|
# 5. Invocation
|
|
result: AdvisorDecision = chain.invoke({
|
|
"passport": passport,
|
|
"profile": profile,
|
|
"description": description,
|
|
"account": account,
|
|
"format_instructions": format_instructions,
|
|
})
|
|
|
|
# 6. Logs et retour
|
|
if result.decision == "Reject":
|
|
log.warning(f"Client rejected. Reason: {result.reason}")
|
|
else:
|
|
log.info("Client accepted.")
|
|
|
|
return result
|