import json import datetime from flask import Blueprint, make_response, request from consts.consts import GPT_4, GPT_DEFAULT, GPT_4O, USER_PREFERENCE_VISIBLE_FIELDS, Claude3 from models.ailog import HistoryLog from schema.schema import UpsertSettingsSchema, DeleteSettingsSchema from handlers.base import check_schema, fail, get_app_id, success from models.user_preference import Preference, UserPreference from models.base import with_session from svc.openai import test_openai_api_key from utils.logging import logging chat2query_bp = Blueprint('chat2query', __name__) def _get_settings(): app_id = get_app_id() preference = UserPreference.load(app_id) result = {} for k, v in preference.to_json().items(): if k in USER_PREFERENCE_VISIBLE_FIELDS: if k == "llm_api_key" and v: # mask the api key, only show the first and last 4 characters vs = v.split(":") for i, part in enumerate(vs): vs[i] = part[:4] + "*" * 10 + part[-4:] v = ":".join(vs) result[k] = v result.pop("openai_api_key", "") result.pop("ai_model", "") return success(result=result) @chat2query_bp.get('/settings') def get_settings(org_id, project_id, app_id): return _get_settings() def _upsert_settings(): app_id = get_app_id() payload = check_schema(UpsertSettingsSchema, request.get_json()) preference = UserPreference.load(app_id) # if llm_api_key is None, means keep the original value, if it's empty string, means remove the value llm_api_key = payload['llm_api_key'] if payload['openai_api_key'] is not None: llm_api_key = payload['openai_api_key'] llm_model = payload['llm_model'] if llm_model is None and payload['ai_model'] is not None: llm_model = payload['ai_model'] # if llm_api_key is None, means keep the original value, if it's empty string, means remove the value if llm_api_key is None: logging.info("llm_api_key is None, keep the original value") llm_api_key = preference.llm_api_key if llm_model in [GPT_4, GPT_4O, Claude3.OPUS] and not llm_api_key: return fail(400, "Setting a model other than GPT-4o-mini requires an OpenAI API key.") if llm_api_key: if llm_model in (GPT_DEFAULT, GPT_4O, GPT_4): try: test_openai_api_key(llm_api_key, llm_model) except Exception as e: logging.info("failed to connect openai: %s", e) return fail(400, "failed to connect OpenAI, invalid OpenAI API key, please check again.") with with_session() as s: final_payload = {} for i in USER_PREFERENCE_VISIBLE_FIELDS: if payload.get(i) is not None: final_payload[i] = payload.get(i) Preference.upsert(s, app_id, final_payload) return _get_settings() @chat2query_bp.put('/settings') def upsert_settings(org_id, project_id, app_id): return _upsert_settings() @chat2query_bp.delete('/settings') def delete_settings(org_id, project_id, app_id): payload = check_schema(DeleteSettingsSchema, request.get_json()) settings = payload['settings'] with with_session() as s: Preference.delete_preference(s, settings, app_id=app_id) return success() def send_result_as_file(result:dict): content = json.dumps(result) filename = "chat2query_logs_{}.json".format(datetime.datetime.now().strftime("%Y%m%d%H%M%S")) response = make_response(content) response.headers['Content-Type'] = 'text/plain' response.headers['Content-Disposition'] = 'attachment; filename={}'.format(filename) return response @chat2query_bp.get("/log") def get_logs(org_id, project_id, app_id): start_at = request.args.get("start", type=int) end_at = request.args.get("end", type=int) status = request.args.get("status") trace_id = request.args.get("request_id") question = request.args.get("question") cluster_id = request.args.get("cluster_id") database = request.args.get("database") _type = request.args.get("type") download = request.args.get("download") == "true" page = max(1, request.args.get("page", 1, type=int)) per_page = 25 # same as dataservices limit, offset = per_page, (page - 1) * per_page logging.info( "get history log, start_at: %s, end_at: %s, status: %s, trace_id: %s, question: %s, cluster_id: %s, database: %s, type: %s, page: %s, limit: %s, offset: %s", start_at, end_at, status, trace_id, question, cluster_id, database, _type, page, limit, offset, ) min_time, max_time = int((datetime.datetime.now() - datetime.timedelta(days=30)).timestamp()), int(datetime.datetime.now().timestamp()) start_at = start_at if start_at else min_time start_at = max(min_time, start_at) start_at = datetime.datetime.fromtimestamp(start_at) end_at = end_at if end_at else max_time end_at = min(max_time, end_at) end_at = datetime.datetime.fromtimestamp(end_at) with with_session() as s: items, total = HistoryLog.search( s, trace_id=trace_id, _type=_type, org_id=org_id, cluster_id=cluster_id, database=database, question=question, start_at=start_at, end_at=end_at, status=status, limit=limit, offset=offset, download=download, ) has_more = total > page * per_page logs = [] for i in items: logs.append({ "type": i._type, "log_time": int(i.created_at.timestamp()), "status": i.status, "request_id": i.trace_id, "latency": int((i.created_at - i.start_at).total_seconds()), "start_at": int(i.start_at.timestamp()), "session_id": i.session_id, "data_summary_id": i.data_summary_id, "cluster_id": i.cluster_id, "database": i.database, "token_count": i.token_count, "input": i.input, "output": i.output, "recalled_knowledge_data": i.recalled_knowledge_data, "settings": i.settings, }) result = { "code": 0, "has_more": has_more, "logs": logs, "message": "", } return result, 200