diff --git a/api/apps/__init__.py b/api/apps/__init__.py index c329679f8fb..4fe5e3cd689 100644 --- a/api/apps/__init__.py +++ b/api/apps/__init__.py @@ -25,6 +25,7 @@ from common.constants import StatusEnum from api.db.db_models import close_connection, APIToken from api.db.services import UserService +from api.db.services.user_session_service import UserSessionService from api.utils.json_encode import CustomJSONEncoder from api.utils import commands @@ -122,6 +123,23 @@ def _load_user(): logging.warning(f"Authentication attempt with invalid token format: {len(access_token)} chars") return None + # Try to get user from UserSession first (supports multiple login sessions) + try: + session_obj = UserSessionService.get_session_by_token(access_token) + if session_obj: + # Update last activity time + UserSessionService.update_last_activity(access_token) + user = UserService.query( + id=session_obj.get("user_id"), status=StatusEnum.VALID.value + ) + if user: + g.user = user[0] + return user[0] + except Exception as session_err: + # UserSession table may not exist yet, or query failed, continue with old method + pass # Silent failure to avoid log clutter + + # Fallback to old method (check user.access_token directly) user = UserService.query( access_token=access_token, status=StatusEnum.VALID.value ) @@ -137,6 +155,7 @@ def _load_user(): return user[0] except Exception as e: logging.warning(f"load_user got exception {e}") + return None current_user = LocalProxy(_load_user) diff --git a/api/apps/user_app.py b/api/apps/user_app.py index e1ad157bc72..91adc0b1aa7 100644 --- a/api/apps/user_app.py +++ b/api/apps/user_app.py @@ -25,6 +25,7 @@ from quart import make_response, redirect, request, session from werkzeug.security import check_password_hash, generate_password_hash +from itsdangerous.url_safe import URLSafeTimedSerializer as Serializer from api.apps.auth import get_auth_client from api.db import FileType, UserTenantRole @@ -33,6 +34,7 @@ from api.db.services.llm_service import get_init_tenant_llm from api.db.services.tenant_llm_service import TenantLLMService from api.db.services.user_service import TenantService, UserService, UserTenantService +from api.db.services.user_session_service import UserSessionService from common.time_utils import current_timestamp, datetime_format, get_format_time from common.misc_utils import download_img, get_uuid from common.constants import RetCode @@ -125,14 +127,42 @@ async def login(): ) elif user: response_data = user.to_json() - user.access_token = get_uuid() login_user(user) user.update_time = current_timestamp() user.update_date = datetime_format(datetime.now()) - user.save() + + # Create session record (supports multiple login sessions) + auth_token = None + try: + device_name = request.headers.get("User-Agent", "Unknown Device") + ip_address = request.headers.get("X-Forwarded-For") or request.remote_addr + success, session_data = UserSessionService.create_session( + user_id=user.id, + device_name=device_name[:255] if device_name else "Unknown Device", + ip_address=ip_address[:45] if ip_address else "Unknown IP" + ) + + if success: + # Use UserSession's token + session_token = session_data.get("access_token") + # Update user.access_token to the same value for consistency + user.access_token = session_token + user.save() + # Update access_token in response data + response_data["access_token"] = session_token + auth_token = user.get_id() # Return JWT encoded token + except Exception as e: + # UserSession table may not exist, use old method + logging.debug(f"UserSession creation failed, using old token method: {e}") + + # If UserSession creation failed, use old method + if not auth_token: + user.access_token = get_uuid() + user.save() + auth_token = user.get_id() + msg = "Welcome back!" - - return await construct_response(data=response_data, auth=user.get_id(), message=msg) + return await construct_response(data=response_data, auth=auth_token, message=msg) else: return get_json_result( data=False, @@ -487,7 +517,7 @@ async def user_info_from_github(access_token): return user_info -@manager.route("/logout", methods=["GET"]) # noqa: F821 +@manager.route("/logout", methods=["POST"]) # noqa: F821 @login_required async def log_out(): """ @@ -497,18 +527,130 @@ async def log_out(): - User security: - ApiKeyAuth: [] + parameters: + - in: body + name: body + required: false + schema: + type: object + properties: + logout_all: + type: boolean + description: Whether to logout all sessions, default false responses: 200: description: Logout successful. schema: type: object """ + # Get current token + jwt = Serializer(secret_key=settings.SECRET_KEY) + authorization = request.headers.get("Authorization") + access_token = str(jwt.loads(authorization)) if authorization else None + + # Check if logout all sessions is requested + logout_all = False + if request.content_length: + request_data = await get_request_json() + logout_all = request_data.get("logout_all", False) + + if logout_all: + # Logout all user sessions + count = UserSessionService.logout_all_sessions(current_user.id) + logging.info(f"User {current_user.email} logged out from {count} sessions") + else: + # Logout only current session + if access_token: + UserSessionService.logout_session(access_token) + + # Invalidate old access_token current_user.access_token = f"INVALID_{secrets.token_hex(16)}" current_user.save() logout_user() return get_json_result(data=True) +@manager.route("/sessions", methods=["GET"]) # noqa: F821 +@login_required +async def get_user_sessions(): + """ + Get all active sessions for the user + --- + tags: + - User + security: + - ApiKeyAuth: [] + responses: + 200: + description: Session list retrieved successfully + schema: + type: object + properties: + data: + type: array + items: + type: object + """ + sessions = UserSessionService.get_user_sessions(current_user.id) + return get_json_result(data=sessions) + + +@manager.route("/sessions/", methods=["DELETE"]) # noqa: F821 +@login_required +async def delete_session(session_id): + """ + Delete a specific session + --- + tags: + - User + security: + - ApiKeyAuth: [] + parameters: + - in: path + name: session_id + required: true + type: string + description: Session ID + responses: + 200: + description: Session deleted successfully + schema: + type: object + """ + # Get session info and validate ownership + from api.db.db_models import UserSession, DB + try: + session_obj = UserSession.select().where( + (UserSession.id == session_id) & + (UserSession.user_id == current_user.id) + ).first() + + if not session_obj: + return get_json_result( + data=False, + code=RetCode.OPERATING_ERROR, + message="Session not found or access denied" + ) + + # Logout this session + success = UserSessionService.logout_session(session_obj.access_token) + if success: + return get_json_result(data=True) + else: + return get_json_result( + data=False, + code=RetCode.OPERATING_ERROR, + message="Failed to delete session" + ) + except Exception as e: + logging.exception(e) + return get_json_result( + data=False, + code=RetCode.EXCEPTION_ERROR, + message=str(e) + ) + + @manager.route("/setting", methods=["POST"]) # noqa: F821 @login_required async def setting_user(): diff --git a/api/db/db_models.py b/api/db/db_models.py index 738e26a06ac..5a727fa8ac3 100644 --- a/api/db/db_models.py +++ b/api/db/db_models.py @@ -595,6 +595,21 @@ def fill_db_model_object(model_object, human_model_dict): return model_object +class UserSession(DataBaseModel): + """User session table, supports multiple login sessions""" + id = CharField(max_length=32, primary_key=True) + user_id = CharField(max_length=32, null=False, index=True) + access_token = CharField(max_length=255, null=False, index=True) + device_name = CharField(max_length=255, null=True, help_text="Device name or browser info") + ip_address = CharField(max_length=45, null=True, help_text="IP address") + is_active = CharField(max_length=1, null=False, default="1", index=True) + last_activity_time = BigIntegerField(null=True, index=True) + expires_at = BigIntegerField(null=True, index=True, help_text="Session expiration time") + + class Meta: + db_table = "user_session" + + class User(DataBaseModel, AuthUser): id = CharField(max_length=32, primary_key=True) access_token = CharField(max_length=255, null=True, index=True) diff --git a/api/db/services/user_session_service.py b/api/db/services/user_session_service.py new file mode 100644 index 00000000000..1b296272422 --- /dev/null +++ b/api/db/services/user_session_service.py @@ -0,0 +1,258 @@ +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import logging +from datetime import datetime + +from api.db.db_models import DB, UserSession +from api.db.services.common_service import CommonService +from common.misc_utils import get_uuid +from common.time_utils import current_timestamp, datetime_format + + +class UserSessionService(CommonService): + """User session service class, supports multiple login sessions""" + model = UserSession + + @classmethod + @DB.connection_context() + def create_session(cls, user_id, device_name=None, ip_address=None, expires_in=2592000): + """ + Create a new user session + + Args: + user_id: User ID + device_name: Device name or browser information + ip_address: IP address + expires_in: Session expiration time (seconds), default 30 days (2592000 seconds) + + Returns: + (success, session_dict): Success flag and session information dictionary + """ + try: + session_id = get_uuid() + access_token = get_uuid() + current_time = current_timestamp() + expires_at = current_time + expires_in if expires_in else None + + session = cls.model( + id=session_id, + user_id=user_id, + access_token=access_token, + device_name=device_name, + ip_address=ip_address, + is_active="1", + last_activity_time=current_time, + expires_at=expires_at, + create_time=current_time, + create_date=datetime_format(datetime.now()), + update_time=current_time, + update_date=datetime_format(datetime.now()) + ) + session.save(force_insert=True) + + return True, session.to_dict() + except Exception as e: + logging.exception(f"Failed to create session: {e}") + return False, {"error": str(e)} + + @classmethod + @DB.connection_context() + def get_session_by_token(cls, access_token): + """ + Get session by access_token + + Args: + access_token: Access token + + Returns: + session dict or None + """ + try: + if not access_token or not str(access_token).strip(): + return None + + session = cls.model.select().where( + (cls.model.access_token == access_token) & + (cls.model.is_active == "1") + ).first() + + if not session: + return None + + # Check expiration: based on last activity time + 30 days (rolling expiration) + if session.last_activity_time: + current_time = current_timestamp() + # Expires after 30 days of inactivity + inactivity_timeout = 2592000 # 30 days + if current_time - session.last_activity_time > inactivity_timeout: + # Session expired, mark as inactive + cls.logout_session(access_token) + return None + + return session.to_dict() + except Exception as e: + logging.exception(f"Failed to get session: {e}") + return None + + @classmethod + @DB.connection_context() + def get_user_sessions(cls, user_id, active_only=True): + """ + Get all sessions for a user + + Args: + user_id: User ID + active_only: Whether to return only active sessions + + Returns: + list of session dicts + """ + try: + query = cls.model.select().where(cls.model.user_id == user_id) + + if active_only: + query = query.where(cls.model.is_active == "1") + + sessions = list(query.order_by(cls.model.last_activity_time.desc()).dicts()) + + # Filter expired sessions: based on last activity time + current_time = current_timestamp() + inactivity_timeout = 2592000 # 30 days + valid_sessions = [] + for session in sessions: + if session.get('last_activity_time'): + if current_time - session['last_activity_time'] > inactivity_timeout: + # Mark as inactive + cls.logout_session(session['access_token']) + continue + valid_sessions.append(session) + + return valid_sessions + except Exception as e: + logging.exception(f"Failed to get user session list: {e}") + return [] + + @classmethod + @DB.connection_context() + def logout_session(cls, access_token): + """ + Logout a specific session + + Args: + access_token: Access token + + Returns: + bool: Success status + """ + try: + updated = cls.model.update( + is_active="0", + update_time=current_timestamp(), + update_date=datetime_format(datetime.now()) + ).where( + cls.model.access_token == access_token + ).execute() + return updated > 0 + except Exception as e: + logging.exception(f"Failed to logout session: {e}") + return False + + @classmethod + @DB.connection_context() + def logout_all_sessions(cls, user_id): + """ + Logout all sessions for a user + + Args: + user_id: User ID + + Returns: + int: Number of sessions logged out + """ + try: + updated = cls.model.update( + is_active="0", + update_time=current_timestamp(), + update_date=datetime_format(datetime.now()) + ).where( + (cls.model.user_id == user_id) & + (cls.model.is_active == "1") + ).execute() + return updated + except Exception as e: + logging.exception(f"Failed to logout all sessions: {e}") + return 0 + + @classmethod + @DB.connection_context() + def update_last_activity(cls, access_token): + """ + Update the last activity time of a session + + Args: + access_token: Access token + + Returns: + bool: Success status + """ + try: + updated = cls.model.update( + last_activity_time=current_timestamp(), + update_time=current_timestamp(), + update_date=datetime_format(datetime.now()) + ).where( + (cls.model.access_token == access_token) & + (cls.model.is_active == "1") + ).execute() + return updated > 0 + except Exception as e: + logging.exception(f"Failed to update session activity time: {e}") + return False + + @classmethod + @DB.connection_context() + def remove_expired_sessions(cls, user_id=None): + """ + Clean up expired sessions (based on last activity time) + + Args: + user_id: User ID (optional), if provided only clean up expired sessions for that user + + Returns: + int: Number of sessions cleaned up + """ + try: + current_time = current_timestamp() + inactivity_timeout = 2592000 # 30 days + expiry_threshold = current_time - inactivity_timeout + + query = cls.model.update( + is_active="0", + update_time=current_time, + update_date=datetime_format(datetime.now()) + ).where( + (cls.model.last_activity_time < expiry_threshold) & + (cls.model.is_active == "1") + ) + + if user_id: + query = query.where(cls.model.user_id == user_id) + + removed = query.execute() + return removed + except Exception as e: + logging.exception(f"Failed to clean up expired sessions: {e}") + return 0 diff --git a/conf/service_conf.yaml b/conf/service_conf.yaml index afd9b98bcb0..3b472e0c240 100644 --- a/conf/service_conf.yaml +++ b/conf/service_conf.yaml @@ -42,7 +42,7 @@ redis: db: 1 username: '' password: 'infini_rag_flow' - host: 'localhost:6379' + host: 'localhost:6378' task_executor: message_queue_type: 'redis' user_default_llm: @@ -105,27 +105,27 @@ user_default_llm: # image2text_model: '' # oauth: # oauth2: -# display_name: "OAuth2" -# client_id: "your_client_id" -# client_secret: "your_client_secret" -# authorization_url: "https://your-oauth-provider.com/oauth/authorize" -# token_url: "https://your-oauth-provider.com/oauth/token" -# userinfo_url: "https://your-oauth-provider.com/oauth/userinfo" -# redirect_uri: "https://your-app.com/v1/user/oauth/callback/oauth2" +# display_name: OAuth2 +# client_id: your_client_id +# client_secret: your_client_secret +# authorization_url: https://your-oauth-provider.com/oauth/authorize +# token_url: https://your-oauth-provider.com/oauth/token +# userinfo_url: https://your-oauth-provider.com/oauth/userinfo +# redirect_uri: https://your-app.com/v1/user/oauth/callback/oauth2 # oidc: -# display_name: "OIDC" -# client_id: "your_client_id" -# client_secret: "your_client_secret" -# issuer: "https://your-oauth-provider.com/oidc" -# scope: "openid email profile" -# redirect_uri: "https://your-app.com/v1/user/oauth/callback/oidc" +# display_name: OIDC +# client_id: your_client_id +# client_secret: your_client_secret +# issuer: https://your-oauth-provider.com/oidc +# scope: openid email profile +# redirect_uri: https://your-app.com/v1/user/oauth/callback/oidc # github: -# type: "github" -# icon: "github" -# display_name: "Github" -# client_id: "your_client_id" -# client_secret: "your_client_secret" -# redirect_uri: "https://your-app.com/v1/user/oauth/callback/github" +# type: github +# icon: github +# display_name: Github +# client_id: your_client_id +# client_secret: your_client_secret +# redirect_uri: https://your-app.com/v1/user/oauth/callback/github # authentication: # client: # switch: false @@ -138,16 +138,16 @@ user_default_llm: # component: false # dataset: false # smtp: -# mail_server: "" +# mail_server: # mail_port: 465 # mail_use_ssl: true # mail_use_tls: false -# mail_username: "" -# mail_password: "" +# mail_username: +# mail_password: # mail_default_sender: -# - "RAGFlow" # display name -# - "" # sender email address -# mail_frontend_url: "https://your-frontend.example.com" +# - RAGFlow # display name +# - # sender email address +# mail_frontend_url: https://your-frontend.example.com # tcadp_config: # secret_id: 'tencent_secret_id' # secret_key: 'tencent_secret_key' diff --git a/conf/service_conf.yaml.template b/conf/service_conf.yaml.template new file mode 100644 index 00000000000..af90ed200f1 --- /dev/null +++ b/conf/service_conf.yaml.template @@ -0,0 +1,152 @@ +ragflow: + host: 0.0.0.0 + http_port: 9380 +admin: + host: 0.0.0.0 + http_port: 9381 +mysql: + name: 'rag_flow' + user: 'root' + password: 'infini_rag_flow' + host: 'localhost' + port: 5455 + max_connections: 900 + stale_timeout: 300 + max_allowed_packet: 1073741824 +minio: + user: 'rag_flow' + password: 'infini_rag_flow' + host: 'localhost:9000' +es: + hosts: 'http://localhost:1200' + username: 'elastic' + password: 'infini_rag_flow' +os: + hosts: 'http://localhost:1201' + username: 'admin' + password: 'infini_rag_flow_OS_01' +infinity: + uri: 'localhost:23817' + db_name: 'default_db' +oceanbase: + scheme: 'oceanbase' # set 'mysql' to create connection using mysql config + config: + db_name: 'test' + user: 'root@ragflow' + password: 'infini_rag_flow' + host: 'localhost' + port: 2881 +redis: + db: 1 + username: '' + password: 'infini_rag_flow' + host: 'localhost:6378' +task_executor: + message_queue_type: 'redis' +user_default_llm: + default_models: + embedding_model: + api_key: 'xxx' + base_url: 'http://localhost:6380' +# postgres: +# name: 'rag_flow' +# user: 'rag_flow' +# password: 'infini_rag_flow' +# host: 'postgres' +# port: 5432 +# max_connections: 100 +# stale_timeout: 30 +# s3: +# access_key: 'access_key' +# secret_key: 'secret_key' +# region: 'region' +#gcs: +# bucket: 'bridgtl-edm-d-bucket-ragflow' +# oss: +# access_key: 'access_key' +# secret_key: 'secret_key' +# endpoint_url: 'http://oss-cn-hangzhou.aliyuncs.com' +# region: 'cn-hangzhou' +# bucket: 'bucket_name' +# azure: +# auth_type: 'sas' +# container_url: 'container_url' +# sas_token: 'sas_token' +# azure: +# auth_type: 'spn' +# account_url: 'account_url' +# client_id: 'client_id' +# secret: 'secret' +# tenant_id: 'tenant_id' +# container_name: 'container_name' +# The OSS object storage uses the MySQL configuration above by default. If you need to switch to another object storage service, please uncomment and configure the following parameters. +# opendal: +# scheme: 'mysql' # Storage type, such as s3, oss, azure, etc. +# config: +# oss_table: 'opendal_storage' +# user_default_llm: +# factory: 'BAAI' +# api_key: 'backup' +# base_url: 'backup_base_url' +# default_models: +# chat_model: +# name: 'qwen2.5-7b-instruct' +# factory: 'xxxx' +# api_key: 'xxxx' +# base_url: 'https://api.xx.com' +# embedding_model: +# api_key: 'xxx' +# base_url: 'http://localhost:6380' +# rerank_model: 'bge-reranker-v2' +# asr_model: +# model: 'whisper-large-v3' # alias of name +# image2text_model: '' +# oauth: +# oauth2: +# display_name: "OAuth2" +# client_id: "your_client_id" +# client_secret: "your_client_secret" +# authorization_url: "https://your-oauth-provider.com/oauth/authorize" +# token_url: "https://your-oauth-provider.com/oauth/token" +# userinfo_url: "https://your-oauth-provider.com/oauth/userinfo" +# redirect_uri: "https://your-app.com/v1/user/oauth/callback/oauth2" +# oidc: +# display_name: "OIDC" +# client_id: "your_client_id" +# client_secret: "your_client_secret" +# issuer: "https://your-oauth-provider.com/oidc" +# scope: "openid email profile" +# redirect_uri: "https://your-app.com/v1/user/oauth/callback/oidc" +# github: +# type: "github" +# icon: "github" +# display_name: "Github" +# client_id: "your_client_id" +# client_secret: "your_client_secret" +# redirect_uri: "https://your-app.com/v1/user/oauth/callback/github" +# authentication: +# client: +# switch: false +# http_app_key: +# http_secret_key: +# site: +# switch: false +# permission: +# switch: false +# component: false +# dataset: false +# smtp: +# mail_server: "" +# mail_port: 465 +# mail_use_ssl: true +# mail_use_tls: false +# mail_username: "" +# mail_password: "" +# mail_default_sender: +# - "RAGFlow" # display name +# - "" # sender email address +# mail_frontend_url: "https://your-frontend.example.com" +# tcadp_config: +# secret_id: 'tencent_secret_id' +# secret_key: 'tencent_secret_key' +# region: 'tencent_region' diff --git a/docker/docker-compose-base.yml b/docker/docker-compose-base.yml index 11104aef53c..63939e8f996 100644 --- a/docker/docker-compose-base.yml +++ b/docker/docker-compose-base.yml @@ -204,7 +204,7 @@ services: command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}", "--maxmemory", "128mb", "--maxmemory-policy", "allkeys-lru"] env_file: .env ports: - - ${REDIS_PORT}:6379 + - 6378:6379 volumes: - redis_data:/data networks: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 62e0ed84801..2944929e914 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -151,10 +151,36 @@ done # ----------------------------------------------------------------------------- # Replace env variables in the service_conf.yaml file # ----------------------------------------------------------------------------- -CONF_DIR="/ragflow/conf" +# Automatically detect whether in Docker container or local development environment +if [ -d "/ragflow/conf" ]; then + CONF_DIR="/ragflow/conf" + PROJECT_ROOT="/ragflow" + IN_DOCKER=1 +else + # Local development environment, use relative paths + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + PROJECT_ROOT="${SCRIPT_DIR}/.." + CONF_DIR="${PROJECT_ROOT}/conf" + IN_DOCKER=0 +fi + +# Switch to project root directory +cd "${PROJECT_ROOT}" + TEMPLATE_FILE="${CONF_DIR}/service_conf.yaml.template" CONF_FILE="${CONF_DIR}/service_conf.yaml" +# Check if template file exists +if [ ! -f "${TEMPLATE_FILE}" ]; then + echo "Error: Template file not found: ${TEMPLATE_FILE}" + echo "Trying alternative location: docker/service_conf.yaml.template" + TEMPLATE_FILE="$(dirname "${BASH_SOURCE[0]}")/service_conf.yaml.template" + if [ ! -f "${TEMPLATE_FILE}" ]; then + echo "Error: Template file not found at alternative location either" + exit 1 + fi +fi + rm -f "${CONF_FILE}" while IFS= read -r line || [[ -n "$line" ]]; do eval "echo \"$line\"" >> "${CONF_FILE}" @@ -208,7 +234,7 @@ ensure_docling if [[ "${ENABLE_WEBSERVER}" -eq 1 ]]; then echo "Starting nginx..." - /usr/sbin/nginx + # /usr/sbin/nginx echo "Starting ragflow_server..." while true; do diff --git a/docker/service_conf.yaml b/docker/service_conf.yaml new file mode 100644 index 00000000000..72e7a6d73fd --- /dev/null +++ b/docker/service_conf.yaml @@ -0,0 +1,153 @@ +ragflow: + host: ${RAGFLOW_HOST:-0.0.0.0} + http_port: 9380 +admin: + host: ${RAGFLOW_HOST:-0.0.0.0} + http_port: 9381 +mysql: + name: '${MYSQL_DBNAME:-rag_flow}' + user: '${MYSQL_USER:-root}' + password: '${MYSQL_PASSWORD:-infini_rag_flow}' + host: '${MYSQL_HOST:-mysql}' + port: 3306 + max_connections: 900 + stale_timeout: 300 + max_allowed_packet: ${MYSQL_MAX_PACKET:-1073741824} +minio: + user: '${MINIO_USER:-rag_flow}' + password: '${MINIO_PASSWORD:-infini_rag_flow}' + host: '${MINIO_HOST:-minio}:9000' +es: + hosts: 'http://${ES_HOST:-es01}:9200' + username: '${ES_USER:-elastic}' + password: '${ELASTIC_PASSWORD:-infini_rag_flow}' +os: + hosts: 'http://${OS_HOST:-opensearch01}:9201' + username: '${OS_USER:-admin}' + password: '${OPENSEARCH_PASSWORD:-infini_rag_flow_OS_01}' +infinity: + uri: '${INFINITY_HOST:-infinity}:23817' + db_name: 'default_db' +oceanbase: + scheme: 'oceanbase' # set 'mysql' to create connection using mysql config + config: + db_name: '${OCEANBASE_DOC_DBNAME:-test}' + user: '${OCEANBASE_USER:-root@ragflow}' + password: '${OCEANBASE_PASSWORD:-infini_rag_flow}' + host: '${OCEANBASE_HOST:-oceanbase}' + port: ${OCEANBASE_PORT:-2881} +redis: + db: 1 + username: '${REDIS_USERNAME:-}' + password: '${REDIS_PASSWORD:-infini_rag_flow}' + host: '${REDIS_HOST:-redis}:6379' +user_default_llm: + default_models: + embedding_model: + api_key: 'xxx' + base_url: 'http://${TEI_HOST}:80' +# postgres: +# name: '${POSTGRES_DBNAME:-rag_flow}' +# user: '${POSTGRES_USER:-rag_flow}' +# password: '${POSTGRES_PASSWORD:-infini_rag_flow}' +# host: '${POSTGRES_HOST:-postgres}' +# port: 5432 +# max_connections: 100 +# stale_timeout: 30 +# s3: +# access_key: 'access_key' +# secret_key: 'secret_key' +# region: 'region' +# endpoint_url: 'endpoint_url' +# bucket: 'bucket' +# prefix_path: 'prefix_path' +# signature_version: 'v4' +# addressing_style: 'path' +# oss: +# access_key: '${ACCESS_KEY}' +# secret_key: '${SECRET_KEY}' +# endpoint_url: '${ENDPOINT}' +# region: '${REGION}' +# bucket: '${BUCKET}' +# prefix_path: '${OSS_PREFIX_PATH}' +# azure: +# auth_type: 'sas' +# container_url: 'container_url' +# sas_token: 'sas_token' +# azure: +# auth_type: 'spn' +# account_url: 'account_url' +# client_id: 'client_id' +# secret: 'secret' +# tenant_id: 'tenant_id' +# container_name: 'container_name' +# The OSS object storage uses the MySQL configuration above by default. If you need to switch to another object storage service, please uncomment and configure the following parameters. +# opendal: +# scheme: 'mysql' # Storage type, such as s3, oss, azure, etc. +# config: +# oss_table: 'opendal_storage' +# user_default_llm: +# factory: 'BAAI' +# api_key: 'backup' +# base_url: 'backup_base_url' +# default_models: +# chat_model: +# name: 'qwen2.5-7b-instruct' +# factory: 'xxxx' +# api_key: 'xxxx' +# base_url: 'https://api.xx.com' +# embedding_model: +# name: 'bge-m3' +# rerank_model: 'bge-reranker-v2' +# asr_model: +# model: 'whisper-large-v3' # alias of name +# image2text_model: '' +# oauth: +# oauth2: +# display_name: "OAuth2" +# client_id: "your_client_id" +# client_secret: "your_client_secret" +# authorization_url: "https://your-oauth-provider.com/oauth/authorize" +# token_url: "https://your-oauth-provider.com/oauth/token" +# userinfo_url: "https://your-oauth-provider.com/oauth/userinfo" +# redirect_uri: "https://your-app.com/v1/user/oauth/callback/oauth2" +# oidc: +# display_name: "OIDC" +# client_id: "your_client_id" +# client_secret: "your_client_secret" +# issuer: "https://your-oauth-provider.com/oidc" +# scope: "openid email profile" +# redirect_uri: "https://your-app.com/v1/user/oauth/callback/oidc" +# github: +# type: "github" +# icon: "github" +# display_name: "Github" +# client_id: "your_client_id" +# client_secret: "your_client_secret" +# redirect_uri: "https://your-app.com/v1/user/oauth/callback/github" +# authentication: +# client: +# switch: false +# http_app_key: +# http_secret_key: +# site: +# switch: false +# permission: +# switch: false +# component: false +# dataset: false +# smtp: +# mail_server: "" +# mail_port: 465 +# mail_use_ssl: true +# mail_use_tls: false +# mail_username: "" +# mail_password: "" +# mail_default_sender: +# - "RAGFlow" # display name +# - "" # sender email address +# mail_frontend_url: "https://your-frontend.example.com" +# tcadp_config: +# secret_id: '${TENCENT_SECRET_ID}' +# secret_key: '${TENCENT_SECRET_KEY}' +# region: '${TENCENT_REGION}' diff --git a/mcp/server/server.py b/mcp/server/server.py index 8350b184b95..9b46816e946 100644 --- a/mcp/server/server.py +++ b/mcp/server/server.py @@ -46,7 +46,7 @@ class Transport(StrEnum): BASE_URL = "http://127.0.0.1:9380" -HOST = "127.0.0.1" +HOST = "" PORT = "9382" HOST_API_KEY = "" MODE = "" diff --git a/rag/raptor.py b/rag/raptor.py index 2d3ccfa7de5..45064f929b5 100644 --- a/rag/raptor.py +++ b/rag/raptor.py @@ -77,7 +77,7 @@ async def _chat(self, system, history, gen_conf): raise last_exc if last_exc else Exception("LLM chat failed without exception") - @timeout(20) + @timeout(20*60) async def _embedding_encode(self, txt): response = await asyncio.to_thread(get_embed_cache, self._embd_model.llm_name, txt) if response is not None: diff --git a/rag/utils/redis_conn.py b/rag/utils/redis_conn.py index fd5903ce115..fc01062a638 100644 --- a/rag/utils/redis_conn.py +++ b/rag/utils/redis_conn.py @@ -136,7 +136,7 @@ def __open__(self): password = self.config.get("password") if password: conn_params["password"] = password - + conn_params["username"] = self.config.get("username", "default") self.REDIS = redis.StrictRedis(**conn_params) self.register_scripts() diff --git a/web/.umirc.ts b/web/.umirc.ts index 044ea303672..13ee9561ac2 100644 --- a/web/.umirc.ts +++ b/web/.umirc.ts @@ -40,7 +40,7 @@ export default defineConfig({ proxy: [ { context: ['/api/v1/admin'], - target: 'http://127.0.0.1:9381/', + target: 'http://127.0.0.1:9380/', changeOrigin: true, ws: true, logger: console,