Files
julius_baer_onboarding/services/advisor.py

152 lines
5.8 KiB
Python
Raw Normal View History

2025-04-12 11:58:01 +02:00
import logging
import threading
import time
2025-04-12 11:58:01 +02:00
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
2025-04-12 11:58:01 +02:00
from services.julius_baer_api_client import JuliusBaerApiClient
from utils.storage.game_files_manager import store_game_round_data
2025-04-13 01:13:20 +02:00
from langchain_openai.chat_models import ChatOpenAI
2025-04-12 22:18:40 +02:00
from validation.llm_validate import AdvisorDecision
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
2025-04-12 11:58:01 +02:00
2025-04-12 13:59:59 +02:00
log = logging.getLogger(__name__)
2025-04-12 11:58:01 +02:00
2025-04-12 22:10:37 +02:00
class Advisor:
2025-04-12 11:58:01 +02:00
def __init__(self):
self.client = JuliusBaerApiClient()
self._thread = None
2025-04-12 11:58:01 +02:00
def start(self):
self.play()
def play_on_separate_thread(self):
if self._thread and self._thread.is_alive():
2025-04-12 13:59:59 +02:00
log.warning('Game loop already running.')
return self._thread
self._thread = threading.Thread(target=self.play, daemon=True)
self._thread.start()
return self._thread
2025-04-12 11:58:01 +02:00
def play(self):
2025-04-12 13:59:59 +02:00
log.info('playing')
2025-04-12 11:58:01 +02:00
payload = GameStartRequestDTO(player_name=config.API_TEAM)
start_response = self.client.start_game(payload)
2025-04-12 13:59:59 +02:00
log.info('game started, session id: %s', start_response.session_id)
2025-04-12 11:58:01 +02:00
client_id = start_response.client_id
2025-04-12 11:58:01 +02:00
decision = self.make_decision(start_response.client_data)
2025-04-12 13:40:51 +02:00
decision_counter = 0
is_game_running = True
while is_game_running:
2025-04-12 11:58:01 +02:00
payload = GameDecisionRequestDTO(
2025-04-12 22:18:40 +02:00
decision=decision.answer,
2025-04-12 11:58:01 +02:00
session_id=start_response.session_id,
client_id=client_id,
2025-04-12 11:58:01 +02:00
)
log.info('client id: %s', client_id)
decision_response = self.client.send_decision(payload)
2025-04-12 13:59:59 +02:00
log.info(f'decision: {decision}, response status: {decision_response.status}, score: {decision_response.score}')
2025-04-12 11:58:01 +02:00
status = decision_response.status
is_game_running = status not in ['gameover', 'complete']
client_id = decision_response.client_id
2025-04-12 11:58:01 +02:00
decision = self.make_decision(decision_response.client_data)
2025-04-12 13:40:51 +02:00
2025-04-12 14:00:11 +02:00
# Handle first response from game initialization logic
if decision_counter == 0:
# Store start response
2025-04-12 22:18:40 +02:00
store_game_round_data(decision.answer, start_response, decision_counter, str(start_response.session_id) ,status)
2025-04-12 14:00:11 +02:00
else:
# store ongoing decision response
2025-04-12 22:18:40 +02:00
store_game_round_data(decision.answer, decision_response, decision_counter, str(start_response.session_id), status)
2025-04-12 13:40:51 +02:00
decision_counter += 1
time.sleep(1)
2025-04-12 14:00:11 +02:00
2025-04-12 22:18:40 +02:00
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
2025-04-12 22:18:40 +02:00
parser = PydanticOutputParser(pydantic_object=AdvisorDecision)
format_instructions = parser.get_format_instructions()
# 3. Prompt enrichi
prompt = ChatPromptTemplate.from_template(
2025-04-13 08:01:31 +02:00
"""You are an experienced compliance analyst at a prestigious private bank. Your role is to carefully examine client applications by cross-checking data from four provided documents: Passport, Profile, Description, and Account.
Your task:
Determine whether to ACCEPT or REJECT the clients private banking application based on data consistency.
CRITICAL RULES for rejection (any single issue means rejection):
- Mismatch in personal details: names, surnames must match exactly across all documents.
- Typos or spelling errors in critical information.
- Expired or incorrect validity dates on passports or other documents.
- Non-existent or incorrect addresses, including city, street, zip code, and country.
- Conflicting information regarding country of domicile.
- Suspicious or implausible personal details.
- Financial discrepancies between Profile and Description documents.
- Mismatching nationality between Passport and Account documents.
ADDITIONAL INSTRUCTIONS:
- Cross-check the Profile, Description, and Account information meticulously against the Passport.
- Historical occupation details may legitimately differ from current datathis alone does not imply inconsistency.
- Always verify currency consistency when evaluating monetary amounts.
- Be extremely cautiousreject immediately if there's any uncertainty or if any detail appears suspicious.
- NEVER fabricate or assume information; rely strictly on provided data.
RESPOND STRICTLY IN THE FOLLOWING JSON FORMAT:
{format_instructions}
---
**Passport Document:**
{passport}
**Profile Document:**
{profile}
**Description Document:**
The following is a single physical document, structured into multiple thematic sections. Each section starts with a heading followed by relevant content.
{description}
**Account Document:**
{account}
"""
)
2025-04-12 11:58:01 +02:00
# 4. Chaîne LLM
2025-04-13 01:13:20 +02:00
chain = prompt | ChatOpenAI(model="gpt-4o-mini") | parser
# 5. Invocation
2025-04-12 22:18:40 +02:00
result: AdvisorDecision = chain.invoke({
"passport": passport,
"profile": profile,
"description": description,
"account": account,
"format_instructions": format_instructions,
})
# 6. Logs et retour
2025-04-12 22:36:21 +02:00
if result.answer == "Reject":
log.warning(f"Client rejected. Reason: {result.reason}")
else:
log.info("Client accepted.")
2025-04-12 22:18:40 +02:00
return result