llm_util.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. # Copyright (C) 2025 AIDC-AI
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS,
  9. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. # See the License for the specific language governing permissions and
  11. # limitations under the License.
  12. """
  13. LLM utility functions for model discovery and connection testing.
  14. Uses the standard OpenAI-compatible /v1/models endpoint.
  15. """
  16. from typing import List, Tuple
  17. import httpx
  18. from loguru import logger
  19. def fetch_available_models(api_key: str, base_url: str, timeout: float = 10.0) -> List[str]:
  20. """
  21. Fetch available models from an OpenAI-compatible API endpoint.
  22. Uses the standard GET /v1/models endpoint with Bearer token authentication.
  23. Args:
  24. api_key: The API key for authentication
  25. base_url: The base URL of the API (e.g., https://api.openai.com/v1)
  26. timeout: Request timeout in seconds
  27. Returns:
  28. List of model IDs available from the API
  29. Raises:
  30. httpx.HTTPStatusError: If the API returns an error status code
  31. httpx.RequestError: If there's a network error
  32. """
  33. # Normalize base_url - ensure it ends with /v1 or similar
  34. base_url = base_url.rstrip("/")
  35. # Build the models endpoint URL
  36. # Handle cases where base_url might or might not include /v1
  37. if base_url.endswith("/v1"):
  38. models_url = f"{base_url}/models"
  39. else:
  40. models_url = f"{base_url}/v1/models"
  41. headers = {
  42. "Authorization": f"Bearer {api_key}",
  43. "Content-Type": "application/json",
  44. }
  45. logger.debug(f"Fetching models from: {models_url}")
  46. with httpx.Client(timeout=timeout) as client:
  47. response = client.get(models_url, headers=headers)
  48. response.raise_for_status()
  49. data = response.json()
  50. models = [model["id"] for model in data.get("data", [])]
  51. # Sort models alphabetically for better UX
  52. models.sort()
  53. logger.debug(f"Fetched {len(models)} models")
  54. return models
  55. def test_llm_connection(api_key: str, base_url: str, timeout: float = 10.0) -> Tuple[bool, str, int]:
  56. """
  57. Test the LLM API connection by attempting to fetch the models list.
  58. Args:
  59. api_key: The API key for authentication
  60. base_url: The base URL of the API
  61. timeout: Request timeout in seconds
  62. Returns:
  63. Tuple of (success: bool, message: str, model_count: int)
  64. - success: True if connection succeeded
  65. - message: Human-readable status message
  66. - model_count: Number of models available (0 if failed)
  67. """
  68. try:
  69. models = fetch_available_models(api_key, base_url, timeout)
  70. return True, f"Connection successful! {len(models)} models available.", len(models)
  71. except httpx.HTTPStatusError as e:
  72. status_code = e.response.status_code
  73. if status_code == 401:
  74. return False, "Authentication failed: Invalid API Key", 0
  75. elif status_code == 403:
  76. return False, "Access forbidden: Check your API Key permissions", 0
  77. elif status_code == 404:
  78. return False, "API endpoint not found: Check your Base URL", 0
  79. else:
  80. return False, f"API error: HTTP {status_code}", 0
  81. except httpx.ConnectError:
  82. return False, "Connection failed: Cannot reach the server", 0
  83. except httpx.TimeoutException:
  84. return False, "Connection timeout: Server did not respond in time", 0
  85. except Exception as e:
  86. logger.error(f"LLM connection test error: {e}")
  87. return False, f"Error: {str(e)}", 0