diff --git a/docker/Dockerfile-slim b/docker/Dockerfile-slim index 3ff7be51..4219c8b0 100644 --- a/docker/Dockerfile-slim +++ b/docker/Dockerfile-slim @@ -36,4 +36,4 @@ RUN git clone https://github.com/hlohaus/deepseek4free.git \ && cd deepseek4free && git checkout 21Feb \ && pip install --no-cache-dir . && cd .. && rm -rf deepseek4free -CMD git pull origin main && docker/update.sh & docker/start.sh \ No newline at end of file +CMD python -m etc.tool.update && docker/update.sh & docker/start.sh \ No newline at end of file diff --git a/docker/update.sh b/docker/update.sh index 9691d971..7653f634 100755 --- a/docker/update.sh +++ b/docker/update.sh @@ -15,7 +15,7 @@ echo "UPDATE: d$c" git pull origin main sleep 120 echo "UPDATE: #$c" -python -m etc.tool.update +git pull origin main sleep 120 done echo "STOPPED." diff --git a/g4f/api/__init__.py b/g4f/api/__init__.py index adb1243b..8c813745 100644 --- a/g4f/api/__init__.py +++ b/g4f/api/__init__.py @@ -188,6 +188,13 @@ class AppConfig: for key, value in data.items(): setattr(cls, key, value) +def remove_authorization(request: Request) -> Request: + new_header = request.headers.mutablecopy() + del new_header["authorization"] + request.scope["headers"] = new_header.raw + delattr(request, "_headers") + return request + class Api: def __init__(self, app: FastAPI) -> None: self.app = app @@ -220,14 +227,16 @@ class Api: session_key = get_session_key() @self.app.middleware("http") async def authorization(request: Request, call_next): + user = None if AppConfig.g4f_api_key is not None or AppConfig.demo: + is_authorization_header = False try: user_g4f_api_key = await self.get_g4f_api_key(request) except HTTPException: user_g4f_api_key = await self.security(request) if hasattr(user_g4f_api_key, "credentials"): user_g4f_api_key = user_g4f_api_key.credentials - user = None + is_authorization_header = True if AppConfig.g4f_api_key is None or not secrets.compare_digest(AppConfig.g4f_api_key, user_g4f_api_key): if has_crypto and user_g4f_api_key: try: @@ -260,10 +269,12 @@ class Api: user = await self.get_username(request) except HTTPException as e: return ErrorResponse.from_message(e.detail, e.status_code, e.headers) - response = await call_next(request) - response.headers["x-user"] = user - return response - return await call_next(request) + if is_authorization_header: + request = remove_authorization(request) + response = await call_next(request) + if user is not None: + response.headers["x_user"] = user + return response def register_validation_exception_handler(self): @self.app.exception_handler(RequestValidationError) diff --git a/g4f/gui/server/backend_api.py b/g4f/gui/server/backend_api.py index 91fcd982..cc6e9666 100644 --- a/g4f/gui/server/backend_api.py +++ b/g4f/gui/server/backend_api.py @@ -214,6 +214,13 @@ class Backend_Api(Api): with cache_file.open("a" if cache_file.exists() else "w") as f: f.write(f"{json.dumps(request.json)}\n") return {} + + @app.route('/backend-api/v2/usage/', methods=['GET']) + def get_usage(date: str): + cache_dir = Path(get_cookies_dir()) / ".usage" + cache_file = cache_dir / f"{date}.jsonl" + print(f"Loading usage data from {cache_file}") + return cache_file.read_text() if cache_file.exists() else (jsonify({"error": {"message": "No usage data found for this date"}}), 404) @app.route('/backend-api/v2/log', methods=['POST']) def add_log(): diff --git a/g4f/tools/run_tools.py b/g4f/tools/run_tools.py index 920b1bb9..adb1377b 100644 --- a/g4f/tools/run_tools.py +++ b/g4f/tools/run_tools.py @@ -5,13 +5,14 @@ import re import json import asyncio import time +import datetime from pathlib import Path -from typing import Optional, Callable, AsyncIterator, Iterator, Dict, Any, Tuple, List, Union +from typing import Optional, AsyncIterator, Iterator, Dict, Any, Tuple, List, Union from ..typing import Messages from ..providers.helper import filter_none from ..providers.asyncio import to_async_iterator -from ..providers.response import Reasoning, FinishReason, Sources +from ..providers.response import Reasoning, FinishReason, Sources, Usage, ProviderInfo from ..providers.types import ProviderType from ..cookies import get_cookies_dir from .web_search import do_search, get_search_message @@ -141,7 +142,7 @@ class AuthManager: env_var = f"{cls.aliases[provider_name].upper()}_API_KEY" api_key = os.environ.get(env_var) if api_key: - debug.log(f"Loading API key from environment variable {env_var}") + print(f"Loading API key for {provider_name} from environment variable {env_var}") return api_key return None @@ -236,9 +237,10 @@ async def async_iter_run_tools( messages, sources = await perform_web_search(messages, web_search) # Get API key - api_key = AuthManager.load_api_key(provider) - if api_key: - kwargs["api_key"] = api_key + if not kwargs.get("api_key"): + api_key = AuthManager.load_api_key(provider) + if api_key: + kwargs["api_key"] = api_key # Process tool calls if tool_calls: @@ -248,9 +250,19 @@ async def async_iter_run_tools( # Generate response response = to_async_iterator(provider.async_create_function(model=model, messages=messages, **kwargs)) + model_info = model async for chunk in response: + if isinstance(chunk, ProviderInfo): + model_info = getattr(chunk, 'model', model_info) + elif isinstance(chunk, Usage): + usage = {"user": kwargs.get("user"), "model": model_info, "provider": provider.get_parent(), **chunk.get_dict()} + usage_dir = Path(get_cookies_dir()) / ".usage" + usage_file = usage_dir / f"{datetime.date.today()}.jsonl" + usage_dir.mkdir(parents=True, exist_ok=True) + with usage_file.open("a" if usage_file.exists() else "w") as f: + f.write(f"{json.dumps(usage)}\n") yield chunk - + # Yield sources if available if sources: yield sources @@ -277,7 +289,7 @@ def iter_run_tools( debug.error(f"Couldn't do web search:", e) # Get API key if needed - if provider is not None: + if not kwargs.get("api_key"): api_key = AuthManager.load_api_key(provider) if api_key: kwargs["api_key"] = api_key @@ -321,7 +333,7 @@ def iter_run_tools( # Process response chunks thinking_start_time = 0 processor = ThinkingProcessor() - + model_info = model for chunk in provider.create_function(model=model, messages=messages, provider=provider, **kwargs): if isinstance(chunk, FinishReason): if sources is not None: @@ -331,6 +343,16 @@ def iter_run_tools( continue elif isinstance(chunk, Sources): sources = None + elif isinstance(chunk, ProviderInfo): + model_info = getattr(chunk, 'model', model_info) + elif isinstance(chunk, Usage): + usage = {"user": kwargs.get("user"), "model": model_info, "provider": provider.get_parent(), **chunk.get_dict()} + usage_dir = Path(get_cookies_dir()) / ".usage" + usage_file = usage_dir / f"{datetime.date.today()}.jsonl" + usage_dir.mkdir(parents=True, exist_ok=True) + with usage_file.open("a" if usage_file.exists() else "w") as f: + f.write(f"{json.dumps(usage)}\n") + if not isinstance(chunk, str): yield chunk continue