Spaces:
Running
Running
ai: Implement dynamic host selection mechanism.
Browse files- app.py +1 -1
- src/client/chat_handler.py +25 -14
- src/client/responses/audio.py +4 -1
- src/client/responses/image.py +4 -1
- src/core/server.py +16 -3
- src/tools/audio.py +71 -108
- src/tools/image.py +71 -97
- src/ui/interface.py +162 -117
- src/utils/session_mapping.py +100 -57
- src/utils/tools.py +68 -48
app.py
CHANGED
@@ -16,4 +16,4 @@ if __name__ == "__main__":
|
|
16 |
|
17 |
# Call the 'launch' method on the 'app' object to start the user interface.
|
18 |
# This typically opens the UI window or begins the event loop, making the application interactive.
|
19 |
-
app.queue(
|
|
|
16 |
|
17 |
# Call the 'launch' method on the 'app' object to start the user interface.
|
18 |
# This typically opens the UI window or begins the event loop, making the application interactive.
|
19 |
+
app.queue().launch(pwa=True)
|
src/client/chat_handler.py
CHANGED
@@ -125,17 +125,28 @@ async def respond(
|
|
125 |
return # Exit function after handling deep search
|
126 |
|
127 |
# For all other inputs that do not match special commands, use the jarvis function to generate a normal response
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
return # Exit function after handling deep search
|
126 |
|
127 |
# For all other inputs that do not match special commands, use the jarvis function to generate a normal response
|
128 |
+
try:
|
129 |
+
async for response in jarvis(
|
130 |
+
session_id=session_id, # Session ID for conversation context
|
131 |
+
model=selected_model, # Selected model for generation
|
132 |
+
history=new_history, # Pass the conversation history
|
133 |
+
user_message=input, # User input message
|
134 |
+
mode=mode, # Use the mode determined by the thinking flag
|
135 |
+
files=files, # Pass any attached files along with the message
|
136 |
+
temperature=temperature, # temperature parameter
|
137 |
+
top_k=top_k, # top_k parameter
|
138 |
+
min_p=min_p, # min_p parameter
|
139 |
+
top_p=top_p, # top_p parameter
|
140 |
+
repetition_penalty=repetition_penalty # repetition_penalty parameter
|
141 |
+
):
|
142 |
+
yield response # Yield each chunk of the response as it is generated by the AI model
|
143 |
+
|
144 |
+
# If all servers have been tried and failed, show the error message
|
145 |
+
except RuntimeError as e:
|
146 |
+
# Inform user of service unavailability
|
147 |
+
yield [{
|
148 |
+
"role": "assistant", # Specify the sender of this message
|
149 |
+
"content": "The server is currently busy. Please wait a moment or try again later"
|
150 |
+
# Provide a clear, user-friendly message explaining
|
151 |
+
}]
|
152 |
+
return # End the function after exhausting all servers
|
src/client/responses/audio.py
CHANGED
@@ -84,7 +84,10 @@ async def audio_integration(
|
|
84 |
"content": (
|
85 |
"Audio generation failed for the user's request. The user tried to generate audio with the instruction: '"
|
86 |
+ audio_instruction + "'\n\n\n"
|
87 |
-
"Please explain to the user that audio generation failed
|
|
|
|
|
|
|
88 |
"Be helpful and empathetic in your response.\n\n\n"
|
89 |
"Use the same language as the previous user input or user request.\n"
|
90 |
"For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
|
|
|
84 |
"content": (
|
85 |
"Audio generation failed for the user's request. The user tried to generate audio with the instruction: '"
|
86 |
+ audio_instruction + "'\n\n\n"
|
87 |
+
"Please explain to the user that the audio generation has failed.\n"
|
88 |
+
"Also, explain all possible reasons why it might have failed.\n"
|
89 |
+
"Additionally, advise the user not to include, input, or attach any sensitive content, "
|
90 |
+
"as this may also cause the audio generation process to fail.\n\n"
|
91 |
"Be helpful and empathetic in your response.\n\n\n"
|
92 |
"Use the same language as the previous user input or user request.\n"
|
93 |
"For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
|
src/client/responses/image.py
CHANGED
@@ -84,7 +84,10 @@ async def image_integration(
|
|
84 |
"content": (
|
85 |
"Image generation failed for the user's request. The user tried to generate an image with the instruction: '"
|
86 |
+ generate_image_instruction + "'\n\n\n"
|
87 |
-
"Please explain to the user that image generation failed
|
|
|
|
|
|
|
88 |
"Be helpful and empathetic in your response.\n\n\n"
|
89 |
"Use the same language as the previous user input or user request.\n"
|
90 |
"For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
|
|
|
84 |
"content": (
|
85 |
"Image generation failed for the user's request. The user tried to generate an image with the instruction: '"
|
86 |
+ generate_image_instruction + "'\n\n\n"
|
87 |
+
"Please explain to the user that the image generation has failed.\n"
|
88 |
+
"Also, explain all possible reasons why it might have failed.\n"
|
89 |
+
"Additionally, advise the user not to include, input, or attach any sensitive content, "
|
90 |
+
"as this may also cause the image generation process to fail.\n\n"
|
91 |
"Be helpful and empathetic in your response.\n\n\n"
|
92 |
"Use the same language as the previous user input or user request.\n"
|
93 |
"For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
|
src/core/server.py
CHANGED
@@ -91,7 +91,14 @@ async def jarvis(
|
|
91 |
|
92 |
# Attempt to stream the response using the primary HTTP transport method (httpx)
|
93 |
try:
|
|
|
94 |
async for chunk in httpx_transport(host, headers, payload, mode): # Stream response chunks asynchronously
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
yield chunk # Yield each chunk to the caller as it arrives for real-time processing
|
96 |
return # Exit the function if streaming completes successfully without errors
|
97 |
|
@@ -108,7 +115,14 @@ async def jarvis(
|
|
108 |
|
109 |
# If the primary transport fails, attempt to stream response using fallback transport (aiohttp)
|
110 |
try:
|
|
|
111 |
async for chunk in aiohttp_transport(host, headers, payload, mode): # Use fallback streaming method
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
yield chunk # Yield streamed chunks to caller as they arrive
|
113 |
return # Exit if fallback transport succeeds
|
114 |
|
@@ -123,6 +137,5 @@ async def jarvis(
|
|
123 |
except Exception: # Catch generic exceptions to avoid crashing
|
124 |
mark(server) # Mark fallback server as failed
|
125 |
|
126 |
-
# If all servers have been tried and failed,
|
127 |
-
|
128 |
-
return # End the function after exhausting all servers
|
|
|
91 |
|
92 |
# Attempt to stream the response using the primary HTTP transport method (httpx)
|
93 |
try:
|
94 |
+
# Iterate over each chunk of data yielded by the asynchronous httpx_transport function
|
95 |
async for chunk in httpx_transport(host, headers, payload, mode): # Stream response chunks asynchronously
|
96 |
+
# Skip any None chunks to prevent errors
|
97 |
+
if chunk is None:
|
98 |
+
continue # Skip this iteration and wait for the next chunk without processing
|
99 |
+
# If the chunk is a dictionary but does not have a "content" key
|
100 |
+
if isinstance(chunk, dict) and "content" not in chunk:
|
101 |
+
chunk["content"] = "" # Add an empty "content" key to maintain a consistent data structure for downstream processing
|
102 |
yield chunk # Yield each chunk to the caller as it arrives for real-time processing
|
103 |
return # Exit the function if streaming completes successfully without errors
|
104 |
|
|
|
115 |
|
116 |
# If the primary transport fails, attempt to stream response using fallback transport (aiohttp)
|
117 |
try:
|
118 |
+
# Asynchronously iterate over chunks yielded by aiohttp_transport
|
119 |
async for chunk in aiohttp_transport(host, headers, payload, mode): # Use fallback streaming method
|
120 |
+
# Skip any None chunks to prevent errors
|
121 |
+
if chunk is None:
|
122 |
+
continue # Skip this iteration and wait for the next chunk without processing
|
123 |
+
# If the chunk is a dictionary but does not have a "content" key
|
124 |
+
if isinstance(chunk, dict) and "content" not in chunk:
|
125 |
+
chunk["content"] = "" # Add an empty "content" key to maintain a consistent data structure for downstream processing
|
126 |
yield chunk # Yield streamed chunks to caller as they arrive
|
127 |
return # Exit if fallback transport succeeds
|
128 |
|
|
|
137 |
except Exception: # Catch generic exceptions to avoid crashing
|
138 |
mark(server) # Mark fallback server as failed
|
139 |
|
140 |
+
# If all servers have been tried and failed, raise an exception
|
141 |
+
raise RuntimeError() # End the function after exhausting all servers
|
|
src/tools/audio.py
CHANGED
@@ -3,128 +3,91 @@
|
|
3 |
# SPDX-License-Identifier: Apache-2.0
|
4 |
#
|
5 |
|
6 |
-
import
|
7 |
-
import
|
8 |
-
import
|
9 |
-
from
|
10 |
-
from src.utils.ip_generator import generate_ip # Import a custom utility function to generate random IP addresses
|
11 |
-
from config import auth # Import authentication credentials or configuration from the config module (not used directly here but imported for completeness)
|
12 |
-
from src.utils.tools import initialize_tools # Import utility function to initialize and retrieve service endpoints or tool URLs
|
13 |
|
14 |
-
# Define a class
|
15 |
class AudioGeneration:
|
16 |
-
# This class provides methods to create audio files based on text instructions and voice parameters
|
17 |
-
|
18 |
"""
|
19 |
-
This class provides
|
20 |
-
It
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
"""
|
22 |
-
|
23 |
-
|
24 |
-
async def create_audio(generate_audio_instruction: str
|
25 |
"""
|
26 |
-
Asynchronously
|
27 |
-
|
28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
Args:
|
30 |
-
generate_audio_instruction (str):
|
31 |
-
|
32 |
-
|
33 |
Returns:
|
34 |
-
str: The URL
|
35 |
-
|
36 |
Raises:
|
37 |
-
Exception:
|
38 |
"""
|
39 |
-
# Encode the
|
40 |
generate_audio_instruct = quote(generate_audio_instruction)
|
41 |
-
|
42 |
-
# Initialize tools and
|
43 |
_, _, audio_tool, poll_token = initialize_tools()
|
44 |
-
|
45 |
-
# Construct the full URL by appending the encoded instruction to the
|
46 |
url = f"{audio_tool}/{generate_audio_instruct}"
|
47 |
-
|
48 |
-
# Define query parameters specifying the audio generation model and voice
|
49 |
params = {
|
50 |
-
"model": "openai-audio", # Specify the
|
51 |
-
"voice":
|
52 |
}
|
53 |
|
54 |
-
#
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
# Generate a random IP address to spoof the client's origin in the request headers
|
59 |
-
headers = {
|
60 |
-
"Authorization": f"Bearer {poll_token}", # Pollinations AI API Token
|
61 |
-
"X-Forwarded-For": generate_ip() # Set the X-Forwarded-For header to a random IP address
|
62 |
-
}
|
63 |
-
|
64 |
-
try:
|
65 |
-
# Perform an asynchronous GET request to the audio generation service with URL, parameters, and headers
|
66 |
-
async with session.get(url, params=params, headers=headers) as resp:
|
67 |
-
# Check if the response status code is 200 (OK) and content type indicates MPEG audio stream
|
68 |
-
content_type = resp.headers.get('Content-Type', '')
|
69 |
-
if resp.status == 200 and 'audio/mpeg' in content_type:
|
70 |
-
# Return the final URL of the generated audio resource as a string
|
71 |
-
return str(resp.url)
|
72 |
-
else:
|
73 |
-
# If the response is not successful or content type is unexpected, wait before retrying
|
74 |
-
await asyncio.sleep(15) # Pause for 15 seconds to avoid overwhelming the server
|
75 |
-
except aiohttp.ClientError:
|
76 |
-
# Catch network-related errors such as connection issues and wait before retrying
|
77 |
-
await asyncio.sleep(15) # Pause for 15 seconds before retrying after an exception
|
78 |
-
|
79 |
-
@staticmethod # Provide an alternative implementation using httpx for flexibility or fallback
|
80 |
-
async def create_audio_httpx(generate_audio_instruction: str, voice: str = "echo") -> str:
|
81 |
-
"""
|
82 |
-
Alternative asynchronous method to generate audio using httpx client.
|
83 |
-
This method also retries indefinitely until a successful response with audio content is received.
|
84 |
-
|
85 |
-
Args:
|
86 |
-
generate_audio_instruction (str): The text instruction to convert into audio.
|
87 |
-
voice (str, optional): Voice style or effect. Defaults to "echo".
|
88 |
-
|
89 |
-
Returns:
|
90 |
-
str: URL of the generated audio file.
|
91 |
-
"""
|
92 |
-
# Encode instruction for safe URL usage
|
93 |
-
generate_audio_instruct = quote(generate_audio_instruction)
|
94 |
-
|
95 |
-
# Initialize tools and get audio generation endpoint
|
96 |
-
_, _, audio_tool, poll_token = initialize_tools()
|
97 |
-
|
98 |
-
# Construct request URL
|
99 |
-
url = f"{audio_tool}/{generate_audio_instruct}"
|
100 |
-
|
101 |
-
# Define query parameters for the request
|
102 |
-
params = {
|
103 |
-
"model": "openai-audio",
|
104 |
-
"voice": voice
|
105 |
}
|
106 |
|
107 |
-
# Create an asynchronous HTTP client
|
108 |
-
async with
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
# Handle network errors and wait before retrying
|
130 |
-
await asyncio.sleep(15)
|
|
|
3 |
# SPDX-License-Identifier: Apache-2.0
|
4 |
#
|
5 |
|
6 |
+
import aiohttp # Import aiohttp library to handle asynchronous HTTP requests and responses
|
7 |
+
from urllib.parse import quote # Import quote function to safely encode URL query parameters
|
8 |
+
from src.utils.ip_generator import generate_ip # Import custom utility to generate random IP addresses for request headers
|
9 |
+
from src.utils.tools import initialize_tools # Import initialize_tools function to set up necessary API tools and retrieve tokens
|
|
|
|
|
|
|
10 |
|
11 |
+
# Define a class to handle audio generation tasks asynchronously
|
12 |
class AudioGeneration:
|
|
|
|
|
13 |
"""
|
14 |
+
This class provides functionality to generate audio content based on textual instructions asynchronously.
|
15 |
+
It encapsulates all necessary steps including preparing the request, encoding parameters, initializing tools,
|
16 |
+
making HTTP requests to the audio generation service, and handling the response.
|
17 |
+
|
18 |
+
The design leverages asynchronous programming to efficiently manage network I/O without blocking the main thread.
|
19 |
+
It uses external utilities for IP generation to simulate diverse client requests and for tool initialization to
|
20 |
+
obtain API endpoints and authorization tokens securely.
|
21 |
+
|
22 |
+
This class is intended for integration in larger applications requiring dynamic audio content creation from text,
|
23 |
+
supporting scalability and responsiveness in asynchronous environments.
|
24 |
"""
|
25 |
+
@staticmethod # Decorator indicating this method does not depend on instance state
|
26 |
+
# Asynchronous method to create an audio based on given instructions and parameters
|
27 |
+
async def create_audio(generate_audio_instruction: str) -> str:
|
28 |
"""
|
29 |
+
Asynchronously generates an audio URL from a given textual instruction describing the desired audio content.
|
30 |
+
|
31 |
+
This method performs the following steps:
|
32 |
+
- Encodes the input text to ensure it is safe for URL transmission
|
33 |
+
- Initializes necessary tools and retrieves the audio generation endpoint and authorization token
|
34 |
+
- Constructs the request URL and query parameters specifying the audio model and voice characteristics
|
35 |
+
- Sets up HTTP headers including authorization and IP address to simulate client diversity
|
36 |
+
- Opens an asynchronous HTTP session and sends a GET request to the audio generation service
|
37 |
+
- Checks the response status and content type to verify successful audio generation
|
38 |
+
- Returns the URL of the generated audio if successful, otherwise raises an exception
|
39 |
+
|
40 |
+
The method is designed to be non-blocking and suitable for use in asynchronous workflows.
|
41 |
+
|
42 |
Args:
|
43 |
+
generate_audio_instruction (str): A textual description or instruction for the audio to be generated
|
44 |
+
|
|
|
45 |
Returns:
|
46 |
+
str: The URL pointing to the generated audio resource
|
47 |
+
|
48 |
Raises:
|
49 |
+
Exception: If the HTTP response indicates failure or the content is not audio
|
50 |
"""
|
51 |
+
# Encode the textual instruction to be safely included in a URL path segment
|
52 |
generate_audio_instruct = quote(generate_audio_instruction)
|
53 |
+
|
54 |
+
# Initialize external tools and retrieve the audio generation endpoint and authorization token
|
55 |
_, _, audio_tool, poll_token = initialize_tools()
|
56 |
+
|
57 |
+
# Construct the full URL by appending the encoded instruction to the audio tool base URL
|
58 |
url = f"{audio_tool}/{generate_audio_instruct}"
|
59 |
+
|
60 |
+
# Define query parameters specifying the audio generation model and voice style to be used
|
61 |
params = {
|
62 |
+
"model": "openai-audio", # Specify the audio generation model identifier
|
63 |
+
"voice": "echo" # Specify the voice style or effect to apply to the generated audio
|
64 |
}
|
65 |
|
66 |
+
# Prepare HTTP headers
|
67 |
+
headers = {
|
68 |
+
"Authorization": f"Bearer {poll_token}", # Bearer token for API authorization
|
69 |
+
"X-Forwarded-For": generate_ip() # Random IP address for request header to simulate client origin
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
}
|
71 |
|
72 |
+
# Create an asynchronous HTTP client session to manage connections efficiently
|
73 |
+
async with aiohttp.ClientSession() as session:
|
74 |
+
"""
|
75 |
+
This block manages the lifecycle of the HTTP client session, ensuring proper connection handling,
|
76 |
+
resource cleanup, and support for asynchronous request execution to avoid blocking the event loop.
|
77 |
+
"""
|
78 |
+
# Send an asynchronous GET request to the audio generation service with specified URL, parameters, and headers
|
79 |
+
async with session.get(
|
80 |
+
url, # The fully constructed URL including the encoded instruction
|
81 |
+
params=params, # Query parameters dictating model and voice options
|
82 |
+
headers=headers # Authorization and client identity headers
|
83 |
+
) as resp:
|
84 |
+
"""
|
85 |
+
This context manages the HTTP response object, allowing inspection of status, headers,
|
86 |
+
and content in an asynchronous manner, suitable for streaming or large payloads.
|
87 |
+
"""
|
88 |
+
# Verify that the response status is HTTP 200 OK and the content type indicates an audio MPEG file
|
89 |
+
if resp.status == 200 and 'audio/mpeg' in resp.headers.get('Content-Type', ''):
|
90 |
+
# Return the final URL from which the generated audio can be accessed or downloaded
|
91 |
+
return str(resp.url)
|
92 |
+
# If the response is not successful or content type is unexpected, raise an exception
|
93 |
+
raise Exception()
|
|
|
|
src/tools/image.py
CHANGED
@@ -3,128 +3,102 @@
|
|
3 |
# SPDX-License-Identifier: Apache-2.0
|
4 |
#
|
5 |
|
6 |
-
import
|
7 |
-
import
|
8 |
-
import
|
9 |
-
from urllib.parse import quote # Import quote to safely encode URL path components
|
10 |
-
from typing import Optional # Import Optional for type hinting parameters that may be None
|
11 |
from src.utils.ip_generator import generate_ip # Import custom utility to generate random IP addresses for request headers
|
12 |
-
from src.utils.tools import initialize_tools # Import
|
13 |
|
14 |
-
# Define a class
|
15 |
class ImageGeneration:
|
16 |
-
# This class provides methods to create image files based on text instructions
|
17 |
-
|
18 |
"""
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
Methods:
|
25 |
-
create_image: Async method to generate an image URL from a text prompt,
|
26 |
-
retrying until successful, using httpx and aiohttp.
|
27 |
"""
|
28 |
-
|
29 |
-
# Supported image formats with their dimensions (width, height)
|
30 |
FORMATS = {
|
31 |
-
"default": (1024, 1024),
|
32 |
-
"square": (1024, 1024),
|
33 |
-
"landscape": (1024, 768),
|
34 |
-
"landscape_large": (1440, 1024),
|
35 |
-
"portrait": (768, 1024),
|
36 |
-
"portrait_large": (1024, 1440),
|
37 |
}
|
38 |
|
39 |
-
@staticmethod
|
|
|
40 |
async def create_image(
|
41 |
-
generate_image_instruction: str, # Text
|
42 |
-
image_format: str = "default", #
|
43 |
-
model: Optional[str] = "flux
|
44 |
-
seed: Optional[int] = None, # Optional seed
|
45 |
-
nologo: bool = True, #
|
46 |
-
private: bool = True, #
|
47 |
-
enhance: bool = True, #
|
48 |
) -> str:
|
49 |
"""
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
seed (Optional[int]): Seed for randomization control.
|
58 |
-
nologo (bool): Flag to exclude logo watermark.
|
59 |
-
private (bool): Flag to mark image as private.
|
60 |
-
enhance (bool): Flag to apply image enhancement.
|
61 |
-
|
62 |
-
Returns:
|
63 |
-
str: URL of the generated image on success.
|
64 |
-
|
65 |
-
Raises:
|
66 |
-
ValueError: If image_format is invalid.
|
67 |
"""
|
68 |
-
# Validate image format
|
69 |
if image_format not in ImageGeneration.FORMATS:
|
70 |
-
raise ValueError("Invalid image format
|
71 |
|
72 |
-
#
|
73 |
width, height = ImageGeneration.FORMATS[image_format]
|
74 |
|
75 |
-
# Initialize tools and
|
76 |
-
_, image_tool,
|
77 |
-
|
78 |
-
# Encode instruction safely for URL path usage
|
79 |
-
generate_image_instruct = quote(generate_image_instruction)
|
80 |
|
81 |
-
# Construct the
|
82 |
-
url = f"{image_tool}{
|
83 |
|
84 |
-
# Prepare query parameters
|
85 |
params = {
|
86 |
-
"width": width,
|
87 |
-
"height": height,
|
88 |
-
"model": model,
|
89 |
-
"nologo":
|
90 |
-
"private":
|
91 |
-
"enhance":
|
92 |
}
|
93 |
|
94 |
-
#
|
95 |
if seed is not None:
|
96 |
params["seed"] = seed
|
97 |
|
98 |
-
# Prepare headers
|
99 |
headers = {
|
100 |
-
"Authorization": f"Bearer {poll_token}", #
|
101 |
"X-Forwarded-For": generate_ip() # Random IP address for request header to simulate client origin
|
102 |
}
|
103 |
|
104 |
-
#
|
105 |
-
async with
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
# Ignore aiohttp errors and retry
|
127 |
-
pass
|
128 |
-
|
129 |
-
# Wait 15 seconds before retrying to avoid overwhelming the server
|
130 |
-
await asyncio.sleep(15)
|
|
|
3 |
# SPDX-License-Identifier: Apache-2.0
|
4 |
#
|
5 |
|
6 |
+
import aiohttp # Import aiohttp library for asynchronous HTTP client functionality
|
7 |
+
from urllib.parse import quote # Import quote function to URL-encode query parameters safely
|
8 |
+
from typing import Optional # Import Optional type hint for parameters that may be None
|
|
|
|
|
9 |
from src.utils.ip_generator import generate_ip # Import custom utility to generate random IP addresses for request headers
|
10 |
+
from src.utils.tools import initialize_tools # Import custom function to initialize necessary tools and tokens
|
11 |
|
12 |
+
# Define a class to handle image generation requests asynchronously
|
13 |
class ImageGeneration:
|
|
|
|
|
14 |
"""
|
15 |
+
This class provides a comprehensive interface for generating images asynchronously via HTTP requests.
|
16 |
+
It supports multiple image formats with predefined dimensions and allows customization of generation parameters such as model type, seed, and enhancement options.
|
17 |
+
The class handles URL encoding, authentication headers, and IP generation to simulate diverse client requests.
|
18 |
+
It uses aiohttp for efficient asynchronous HTTP client sessions and manages error handling for invalid inputs and HTTP response failures.
|
|
|
|
|
|
|
|
|
19 |
"""
|
20 |
+
# Dictionary mapping image format names to their corresponding width and height dimensions
|
|
|
21 |
FORMATS = {
|
22 |
+
"default": (1024, 1024), # Default square format with 1024x1024 pixels
|
23 |
+
"square": (1024, 1024), # Square format identical to default size
|
24 |
+
"landscape": (1024, 768), # Landscape format with width greater than height
|
25 |
+
"landscape_large": (1440, 1024), # Larger landscape format for higher resolution images
|
26 |
+
"portrait": (768, 1024), # Portrait format with height greater than width
|
27 |
+
"portrait_large": (1024, 1440), # Larger portrait format for higher resolution images
|
28 |
}
|
29 |
|
30 |
+
@staticmethod # Decorator indicating this method does not depend on instance state
|
31 |
+
# Asynchronous method to create an image based on given instructions and parameters
|
32 |
async def create_image(
|
33 |
+
generate_image_instruction: str, # Text instruction describing the image to generate
|
34 |
+
image_format: str = "default", # Desired image format key from FORMATS dictionary
|
35 |
+
model: Optional[str] = "flux", # Optional model identifier controlling generation style or algorithm
|
36 |
+
seed: Optional[int] = None, # Optional random seed to reproduce specific image outputs
|
37 |
+
nologo: bool = True, # Flag to remove logo watermark from generated image
|
38 |
+
private: bool = True, # Flag to mark image generation as private, restricting visibility
|
39 |
+
enhance: bool = True, # Flag to apply enhancement filters to improve image quality
|
40 |
) -> str:
|
41 |
"""
|
42 |
+
This asynchronous method generates an image URL based on detailed instructions and customization options.
|
43 |
+
It validates the requested image format against supported formats, retrieves corresponding dimensions,
|
44 |
+
initializes necessary tools and authentication tokens, constructs the request URL with encoded parameters,
|
45 |
+
and performs an HTTP GET request asynchronously using aiohttp.
|
46 |
+
The method includes headers for authorization and IP address to simulate requests from different clients.
|
47 |
+
On successful response (HTTP 200), it returns the final URL of the generated image.
|
48 |
+
If the response status is not successful, it raise an exception.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
"""
|
50 |
+
# Validate if the requested image format is supported, otherwise raise an error
|
51 |
if image_format not in ImageGeneration.FORMATS:
|
52 |
+
raise ValueError("Invalid image format")
|
53 |
|
54 |
+
# Retrieve width and height dimensions for the requested image format
|
55 |
width, height = ImageGeneration.FORMATS[image_format]
|
56 |
|
57 |
+
# Initialize external tools and retrieve the image tool URL and authorization token
|
58 |
+
_, image_tool, _, poll_token = initialize_tools()
|
|
|
|
|
|
|
59 |
|
60 |
+
# Construct the base URL for the image generation request with URL-encoded instruction
|
61 |
+
url = f"{image_tool}{quote(generate_image_instruction)}"
|
62 |
|
63 |
+
# Prepare query parameters dictionary for the HTTP request with all relevant options
|
64 |
params = {
|
65 |
+
"width": width, # Image width in pixels
|
66 |
+
"height": height, # Image height in pixels
|
67 |
+
"model": model, # Model identifier string for image generation
|
68 |
+
"nologo": str(nologo).lower(), # Convert boolean to lowercase string for query parameter
|
69 |
+
"private": str(private).lower(), # Convert boolean to lowercase string for query parameter
|
70 |
+
"enhance": str(enhance).lower(), # Convert boolean to lowercase string for query parameter
|
71 |
}
|
72 |
|
73 |
+
# If a seed value is provided, add it to the query parameters to control randomness
|
74 |
if seed is not None:
|
75 |
params["seed"] = seed
|
76 |
|
77 |
+
# Prepare HTTP headers
|
78 |
headers = {
|
79 |
+
"Authorization": f"Bearer {poll_token}", # Bearer token for API authentication
|
80 |
"X-Forwarded-For": generate_ip() # Random IP address for request header to simulate client origin
|
81 |
}
|
82 |
|
83 |
+
# Create an asynchronous HTTP client session to perform the GET request
|
84 |
+
async with aiohttp.ClientSession() as session:
|
85 |
+
"""
|
86 |
+
This context manager ensures that the HTTP session is properly opened and closed asynchronously,
|
87 |
+
enabling efficient connection reuse and resource cleanup after the request completes.
|
88 |
+
"""
|
89 |
+
# Perform the HTTP GET request asynchronously with the constructed URL, parameters, and headers
|
90 |
+
async with session.get(
|
91 |
+
url, # The fully constructed URL including encoded instruction
|
92 |
+
params=params, # Query parameters controlling image generation options
|
93 |
+
headers=headers # Authorization and client identity headers
|
94 |
+
) as resp:
|
95 |
+
"""
|
96 |
+
This context manager handles the HTTP response asynchronously.
|
97 |
+
It waits for the server's response and manages the response lifecycle.
|
98 |
+
"""
|
99 |
+
# Check if the HTTP response status indicates success
|
100 |
+
if resp.status == 200 and 'image/' in resp.headers.get('Content-Type', ''):
|
101 |
+
# Return the final URL of the generated image as a string
|
102 |
+
return str(resp.url)
|
103 |
+
# If the response is not successful or content type is unexpected, raise an exception
|
104 |
+
raise Exception()
|
|
|
|
|
|
|
|
|
|
src/ui/interface.py
CHANGED
@@ -3,159 +3,209 @@
|
|
3 |
# SPDX-License-Identifier: Apache-2.0
|
4 |
#
|
5 |
|
6 |
-
import
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
#
|
|
|
|
|
|
|
|
|
12 |
def ui():
|
13 |
"""
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
18 |
"""
|
19 |
-
#
|
20 |
-
|
21 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
with gr.Sidebar(open=False):
|
23 |
-
#
|
24 |
model_precision = gr.Dropdown(
|
25 |
-
choices=list(model.keys()), #
|
26 |
-
label="Model Precision", #
|
27 |
info=(
|
28 |
-
#
|
29 |
"The smaller the value, the faster the response but less accurate. "
|
30 |
"Conversely, the larger the value, the response is slower but more accurate."
|
31 |
),
|
32 |
-
value="Q8_K_XL" #
|
33 |
)
|
34 |
-
|
35 |
-
# Checkbox to enable or disable reasoning mode, which toggles the AI's "thinking" capability
|
36 |
reasoning = gr.Checkbox(
|
37 |
-
label="Reasoning", #
|
38 |
-
info="Switching between thinking and non-thinking mode.", #
|
39 |
-
value=True #
|
40 |
)
|
41 |
-
|
42 |
-
# Slider controlling the 'Temperature' parameter, affecting randomness in AI responses, initially non-interactive
|
43 |
temperature = gr.Slider(
|
44 |
-
minimum=0.0, #
|
45 |
-
maximum=2.0, #
|
46 |
-
step=0.01, #
|
47 |
-
label="Temperature", # Label
|
48 |
-
interactive=False #
|
49 |
)
|
50 |
-
|
51 |
-
# Slider controlling the 'Top K' parameter, which limits the number of highest probability tokens considered, non-interactive initially
|
52 |
top_k = gr.Slider(
|
53 |
-
minimum=0,
|
54 |
-
maximum=100,
|
55 |
-
step=1,
|
56 |
-
label="Top K",
|
57 |
-
interactive=False
|
58 |
)
|
59 |
-
|
60 |
-
# Slider for 'Min P' parameter, representing minimum cumulative probability threshold, non-interactive initially
|
61 |
min_p = gr.Slider(
|
62 |
-
minimum=0.0,
|
63 |
-
maximum=1.0,
|
64 |
-
step=0.01,
|
65 |
-
label="Min P",
|
66 |
-
interactive=False
|
67 |
)
|
68 |
-
|
69 |
-
# Slider for 'Top P' parameter, controlling nucleus sampling probability, non-interactive initially
|
70 |
top_p = gr.Slider(
|
71 |
-
minimum=0.0,
|
72 |
-
maximum=1.0,
|
73 |
-
step=0.01,
|
74 |
-
label="Top P",
|
75 |
-
interactive=False
|
76 |
)
|
77 |
-
|
78 |
-
# Slider for 'Repetition Penalty' parameter to reduce repetitive text generation, non-interactive initially
|
79 |
repetition_penalty = gr.Slider(
|
80 |
-
minimum=0.1,
|
81 |
-
maximum=2.0,
|
82 |
-
step=0.01,
|
83 |
-
label="Repetition Penalty",
|
84 |
-
interactive=False
|
85 |
)
|
86 |
-
|
87 |
-
# Define a function to update the model parameter sliders based on the reasoning checkbox state
|
88 |
def update_parameters(switching):
|
89 |
"""
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
Returns:
|
96 |
-
tuple: Updated values for temperature, top_k, min_p, top_p, and repetition_penalty sliders.
|
97 |
"""
|
98 |
-
# Call the 'parameters' function
|
99 |
return parameters(switching)
|
100 |
-
|
101 |
-
# Set up an event listener to update parameter sliders when the reasoning checkbox state changes
|
102 |
reasoning.change(
|
103 |
-
fn=update_parameters, #
|
104 |
-
inputs=[reasoning], #
|
105 |
-
outputs=[temperature, top_k, min_p, top_p, repetition_penalty], # Update
|
106 |
-
api_name=False # Disable API
|
107 |
)
|
108 |
-
|
109 |
-
# Initialize the parameter sliders with values corresponding to the default reasoning checkbox state
|
110 |
values = parameters(reasoning.value)
|
111 |
temperature.value, top_k.value, min_p.value, top_p.value, repetition_penalty.value = values
|
112 |
-
|
113 |
-
# Checkbox to enable or disable the image generation feature in the chat interface
|
114 |
image_generation = gr.Checkbox(
|
115 |
-
label="Image Generation", #
|
116 |
info=(
|
117 |
-
#
|
118 |
"Type <i><b>/image</b></i> followed by the instructions to start generating an image."
|
119 |
),
|
120 |
-
value=True #
|
121 |
)
|
122 |
-
|
123 |
-
# Checkbox to enable or disable the audio generation feature in the chat interface
|
124 |
audio_generation = gr.Checkbox(
|
125 |
-
label="Audio Generation",
|
126 |
info=(
|
|
|
127 |
"Type <i><b>/audio</b></i> followed by the instructions to start generating audio."
|
128 |
),
|
129 |
-
value=True
|
130 |
)
|
131 |
-
|
132 |
-
# Checkbox to enable or disable the deep web search feature in the chat interface
|
133 |
search_generation = gr.Checkbox(
|
134 |
-
label="Deep Search",
|
135 |
info=(
|
|
|
136 |
"Type <i><b>/dp</b></i> followed by the instructions to search the web."
|
137 |
),
|
138 |
-
value=True
|
139 |
)
|
140 |
-
|
141 |
-
# Create the main chat interface where users interact with the AI assistant
|
142 |
gr.ChatInterface(
|
143 |
-
fn=respond, #
|
|
|
144 |
additional_inputs=[
|
145 |
-
#
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
search_generation
|
156 |
],
|
|
|
157 |
examples=[
|
158 |
-
# Predefined example inputs to help users quickly test the assistant's features
|
159 |
["Please introduce yourself."],
|
160 |
["/audio Could you explain what Artificial Intelligence (AI) is?"],
|
161 |
["/audio What is Hugging Face?"],
|
@@ -170,20 +220,15 @@ def ui():
|
|
170 |
["Please generate a highly complex code snippet on any topic."],
|
171 |
["Explain about quantum computers."]
|
172 |
],
|
173 |
-
cache_examples=False, # Disable caching of example outputs to
|
174 |
chatbot=gr.Chatbot(
|
175 |
-
label="J.A.R.V.I.S.", #
|
176 |
-
show_copy_button=True, #
|
177 |
-
scale=1, #
|
178 |
-
allow_tags=["think"]
|
179 |
),
|
180 |
-
multimodal=False,
|
181 |
-
|
182 |
-
fill_width=True, # Duplicate from Blocks to Chat Interface
|
183 |
-
head=meta_tags, # Duplicate from Blocks to Chat Interface
|
184 |
-
show_progress="full", # Progress animation
|
185 |
-
api_name="api", # API endpoint
|
186 |
-
concurrency_limit=None # Queuing
|
187 |
)
|
188 |
-
# Return the
|
189 |
return app
|
|
|
3 |
# SPDX-License-Identifier: Apache-2.0
|
4 |
#
|
5 |
|
6 |
+
import logging # Import the logging module to enable tracking of events, errors, and debugging information during program execution
|
7 |
+
import warnings # Import the warnings module to manage and control warning messages generated by the code or libraries
|
8 |
+
import os # Import the os module to interact with the operating system, such as file system operations and environment variables
|
9 |
+
import sys # Import the sys module to access system-specific parameters and functions, including command-line arguments and interpreter information
|
10 |
+
import gradio as gr # Import the Gradio library to build interactive web interfaces for machine learning applications, enabling easy deployment and testing
|
11 |
+
from src.core.parameter import parameters # Import the 'parameters' function from the core parameter module, which provides model parameter configurations based on the selected reasoning mode
|
12 |
+
from src.client.chat_handler import respond # Import the 'respond' function from the chat handler module, responsible for generating AI assistant responses to user inputs
|
13 |
+
from config import model, meta_tags # Import 'model' dictionary containing available model precision options and their details, and 'meta_tags' dictionary containing HTML meta tag information for web interface setup
|
14 |
+
|
15 |
+
# Define the main user interface function for the Gradio application
|
16 |
def ui():
|
17 |
"""
|
18 |
+
This function constructs and configures the entire user interface for the AI assistant web application using the Gradio Blocks API.
|
19 |
+
The function is responsible for setting up logging and warning filters, suppressing unwanted output, and building a highly interactive sidebar for model configuration.
|
20 |
+
It provides users with a comprehensive set of controls to adjust model precision, reasoning mode, and advanced generation parameters such as temperature, top_k, min_p, top_p, and repetition penalty.
|
21 |
+
The sidebar also allows users to enable or disable additional features like image generation, audio generation, and deep web search, each with descriptive tooltips to guide user interaction.
|
22 |
+
The function dynamically updates parameter sliders based on the state of the reasoning checkbox, ensuring that the UI always reflects the current configuration.
|
23 |
+
The main chat interface is constructed with a set of example prompts to help users explore the assistant's capabilities, and all configuration states are passed as additional inputs to the response handler.
|
24 |
+
Output and error streams are redirected to prevent extraneous log messages from appearing in the web interface, creating a clean and focused user experience.
|
25 |
+
The function returns the fully constructed Gradio app object, ready to be launched or embedded as needed.
|
26 |
+
This approach ensures the application is robust, user-friendly, and highly customizable, making it suitable for both demonstration and production use
|
27 |
"""
|
28 |
+
# Suppress all warning messages globally to prevent them from displaying in the application output and confusing users
|
29 |
+
warnings.filterwarnings(
|
30 |
+
"ignore" # Specify the action to ignore all warnings, ensuring a clean user interface free from warning clutter
|
31 |
+
)
|
32 |
+
# Set the warnings filter to ignore warnings by default, providing an additional layer of suppression for any warnings that might arise during runtime
|
33 |
+
warnings.simplefilter(
|
34 |
+
"ignore" # Apply the ignore filter to all warning categories, further ensuring that no warnings are shown to users
|
35 |
+
)
|
36 |
+
# Configure the logging system to only display messages at the CRITICAL level or higher, effectively hiding informational, warning, and error logs from the application's output
|
37 |
+
logging.basicConfig(
|
38 |
+
level=logging.CRITICAL # Set the logging threshold to CRITICAL, which is the highest severity level, to minimize log output
|
39 |
+
)
|
40 |
+
# Define a list of logger names associated with common web and async frameworks used in Gradio apps
|
41 |
+
logger_keywords = [
|
42 |
+
"uvicorn", # Uvicorn is an ASGI web server commonly used with FastAPI and Gradio
|
43 |
+
"fastapi", # FastAPI is a modern web framework for building APIs with Python
|
44 |
+
"gradio", # Gradio's own logger, which may emit internal messages
|
45 |
+
"httpx", # HTTPX is an HTTP client for Python, often used for making web requests
|
46 |
+
"aiohttp", # Aiohttp is an asynchronous HTTP client and server library
|
47 |
+
"asyncio", # Asyncio is Python's standard library for asynchronous programming
|
48 |
+
"starlette", # Starlette is a lightweight ASGI framework used by FastAPI
|
49 |
+
"anyio", # AnyIO is an asynchronous networking and concurrency library
|
50 |
+
]
|
51 |
+
# Iterate through all registered loggers in the logging system to suppress their output if they match any of the specified keywords
|
52 |
+
for name, logger in logging.root.manager.loggerDict.items():
|
53 |
+
# Check if the logger's name contains any of the keywords from the list, indicating it is associated with a third-party library whose logs should be suppressed
|
54 |
+
if any(k in name for k in logger_keywords):
|
55 |
+
# Ensure the logger object is an instance of the Logger class before attempting to set its level
|
56 |
+
if isinstance(
|
57 |
+
logger, # The logger object retrieved from the logger dictionary
|
58 |
+
logging.Logger # The Logger class from the logging module
|
59 |
+
):
|
60 |
+
# Set the logger's level to CRITICAL to prevent it from emitting lower-severity log messages during application execution
|
61 |
+
logger.setLevel(
|
62 |
+
logging.CRITICAL # Only allow critical errors to be logged, hiding all informational and warning messages
|
63 |
+
)
|
64 |
+
# Redirect the standard output stream to the null device, effectively silencing all print statements and unwanted output from third-party libraries
|
65 |
+
sys.stdout = open(
|
66 |
+
os.devnull, "w" # Open the operating system's null device in write mode, discarding any data written to it
|
67 |
+
)
|
68 |
+
# Redirect the standard error stream to the null device, ensuring that error messages and tracebacks do not appear in the application's output
|
69 |
+
sys.stderr = open(
|
70 |
+
os.devnull, "w" # Open the null device for error output, suppressing all error messages from being displayed to users
|
71 |
+
)
|
72 |
+
# Create the main Gradio Blocks application, which serves as the container for all UI components and layout
|
73 |
+
with gr.Blocks(
|
74 |
+
fill_height=True, # Automatically adjust the block's height to fill the available vertical space in the browser window, ensuring a responsive design
|
75 |
+
fill_width=True, # Automatically adjust the block's width to fill the available horizontal space, maximizing the use of screen real estate
|
76 |
+
head=meta_tags # Inject custom meta tags into the HTML head section, allowing for SEO optimization
|
77 |
+
) as app:
|
78 |
+
"""
|
79 |
+
This code block initializes the main Gradio Blocks context, which acts as the root container for all user interface components in the application.
|
80 |
+
The fill_height and fill_width parameters ensure that the app dynamically resizes to fit the user's browser window, providing a seamless and visually appealing experience.
|
81 |
+
The head parameter inserts custom meta tags into the HTML head, enabling advanced customization such as setting the page title, description, favicon, or viewport settings.
|
82 |
+
All UI elements, including the sidebar configuration controls and the main chat interface, are defined within this context to ensure they are properly managed and rendered by Gradio.
|
83 |
+
The resulting 'app' object encapsulates the entire interactive application, making it easy to launch or embed as needed
|
84 |
+
"""
|
85 |
+
# Begin the sidebar section, which slides in from the left and contains all model configuration controls. The sidebar starts in a closed state for a cleaner initial appearance
|
86 |
with gr.Sidebar(open=False):
|
87 |
+
# Create a dropdown menu that allows users to select the desired model precision from the available options defined in the 'model' dictionary
|
88 |
model_precision = gr.Dropdown(
|
89 |
+
choices=list(model.keys()), # Populate the dropdown with the keys from the model dictionary, such as "F16", "F32", or custom precision names
|
90 |
+
label="Model Precision", # Display this label above the dropdown to clearly indicate its purpose to users
|
91 |
info=(
|
92 |
+
# Provide a tooltip that explains the impact of different precision settings on model speed and accuracy, helping users make informed choices
|
93 |
"The smaller the value, the faster the response but less accurate. "
|
94 |
"Conversely, the larger the value, the response is slower but more accurate."
|
95 |
),
|
96 |
+
value="Q8_K_XL" # Set the default selected value to "Q8_K_XL", which represents a specific model precision configuration
|
97 |
)
|
98 |
+
# Add a checkbox that enables or disables reasoning mode, which affects how the AI assistant processes user queries and generates responses
|
|
|
99 |
reasoning = gr.Checkbox(
|
100 |
+
label="Reasoning", # Show this label next to the checkbox to indicate its function
|
101 |
+
info="Switching between thinking and non-thinking mode.", # Display a tooltip explaining that this option toggles the AI's ability to perform complex reasoning
|
102 |
+
value=True # Set the default state to enabled, so the AI uses reasoning mode by default
|
103 |
)
|
104 |
+
# Create a slider for the 'Temperature' parameter, which controls the randomness of the AI's text generation. The slider is initially non-interactive and updated dynamically based on reasoning mode
|
|
|
105 |
temperature = gr.Slider(
|
106 |
+
minimum=0.0, # Allow the temperature value to range from 0.0, representing deterministic output
|
107 |
+
maximum=2.0, # Set the upper limit to 2.0, allowing for highly random and creative responses
|
108 |
+
step=0.01, # Allow fine-grained adjustments in increments of 0.01 for precise control
|
109 |
+
label="Temperature", # Label the slider so users know what parameter they are adjusting
|
110 |
+
interactive=False # Disable direct user interaction, as the value is set programmatically when reasoning mode changes
|
111 |
)
|
112 |
+
# Add a slider for the 'Top K' parameter, which limits the number of highest-probability tokens considered during text generation. This helps control output diversity
|
|
|
113 |
top_k = gr.Slider(
|
114 |
+
minimum=0, # Allow the top_k value to start at 0, which may disable the filter
|
115 |
+
maximum=100, # Set the maximum to 100, providing a wide range for experimentation with model output diversity
|
116 |
+
step=1, # Adjust the value in whole number increments for clarity
|
117 |
+
label="Top K", # Label the slider for user understanding
|
118 |
+
interactive=False # Make the slider non-interactive, as it is updated based on reasoning mode
|
119 |
)
|
120 |
+
# Create a slider for the 'Min P' parameter, representing the minimum cumulative probability threshold for token selection during generation
|
|
|
121 |
min_p = gr.Slider(
|
122 |
+
minimum=0.0, # Allow the minimum probability to start at 0.0, including all tokens
|
123 |
+
maximum=1.0, # Set the maximum to 1.0, representing the full probability mass
|
124 |
+
step=0.01, # Use small increments for precise tuning
|
125 |
+
label="Min P", # Label the slider to clarify its function
|
126 |
+
interactive=False # Disable direct interaction, as it is controlled programmatically
|
127 |
)
|
128 |
+
# Add a slider for the 'Top P' parameter, which controls nucleus sampling by limiting the cumulative probability of selected tokens
|
|
|
129 |
top_p = gr.Slider(
|
130 |
+
minimum=0.0, # Allow the top_p value to start at 0.0, which may restrict output to only the most probable token
|
131 |
+
maximum=1.0, # Set the upper limit to 1.0, including all tokens in the sampling pool
|
132 |
+
step=0.01, # Enable fine adjustments for optimal output control
|
133 |
+
label="Top P", # Label the slider accordingly
|
134 |
+
interactive=False # Make the slider non-interactive, as it is set based on reasoning mode
|
135 |
)
|
136 |
+
# Create a slider for the 'Repetition Penalty' parameter, which discourages the model from generating repetitive text by penalizing repeated tokens
|
|
|
137 |
repetition_penalty = gr.Slider(
|
138 |
+
minimum=0.1, # Set the minimum penalty to 0.1, allowing for low but non-zero penalties
|
139 |
+
maximum=2.0, # Allow penalties up to 2.0, strongly discouraging repetition
|
140 |
+
step=0.01, # Use small increments for precise adjustment
|
141 |
+
label="Repetition Penalty", # Label the slider for clarity
|
142 |
+
interactive=False # Make the slider non-interactive, as it is updated programmatically
|
143 |
)
|
144 |
+
# Define a function to update all parameter sliders when the reasoning checkbox is toggled by the user
|
|
|
145 |
def update_parameters(switching):
|
146 |
"""
|
147 |
+
This function is triggered whenever the user changes the state of the reasoning checkbox in the sidebar.
|
148 |
+
It calls the 'parameters' function, passing the new reasoning state, to retrieve a tuple of updated values for temperature, top_k, min_p, top_p, and repetition penalty.
|
149 |
+
The returned values are then used to update the corresponding sliders in the sidebar, ensuring that the UI always reflects the current configuration for the selected reasoning mode.
|
150 |
+
This dynamic updating mechanism provides immediate feedback to users and helps prevent configuration mismatches, improving the overall user experience and reliability of the application
|
|
|
|
|
|
|
151 |
"""
|
152 |
+
# Call the external 'parameters' function with the current reasoning state to get updated parameter values
|
153 |
return parameters(switching)
|
154 |
+
# Set up an event listener that calls 'update_parameters' whenever the reasoning checkbox state changes, updating all related sliders with new values
|
|
|
155 |
reasoning.change(
|
156 |
+
fn=update_parameters, # Specify the function to call when the checkbox value changes
|
157 |
+
inputs=[reasoning], # Pass the current value of the reasoning checkbox as input
|
158 |
+
outputs=[temperature, top_k, min_p, top_p, repetition_penalty], # Update all relevant sliders with the new parameter values
|
159 |
+
api_name=False # Disable API exposure for this event, as it is only used internally by the UI
|
160 |
)
|
161 |
+
# Initialize all parameter sliders with values corresponding to the default state of the reasoning checkbox, ensuring the UI is consistent on first load
|
|
|
162 |
values = parameters(reasoning.value)
|
163 |
temperature.value, top_k.value, min_p.value, top_p.value, repetition_penalty.value = values
|
164 |
+
# Add a checkbox to enable or disable image generation capabilities in the chat interface, allowing users to control access to this feature
|
|
|
165 |
image_generation = gr.Checkbox(
|
166 |
+
label="Image Generation", # Display this label next to the checkbox for clarity
|
167 |
info=(
|
168 |
+
# Provide a tooltip explaining how to trigger image generation using a special chat command
|
169 |
"Type <i><b>/image</b></i> followed by the instructions to start generating an image."
|
170 |
),
|
171 |
+
value=True # Enable image generation by default for user convenience
|
172 |
)
|
173 |
+
# Add a checkbox to enable or disable audio generation in the chat, giving users control over this feature
|
|
|
174 |
audio_generation = gr.Checkbox(
|
175 |
+
label="Audio Generation", # Label the checkbox for clarity
|
176 |
info=(
|
177 |
+
# Tooltip instructing users to use the /audio command to generate audio responses
|
178 |
"Type <i><b>/audio</b></i> followed by the instructions to start generating audio."
|
179 |
),
|
180 |
+
value=True # Enable audio generation by default
|
181 |
)
|
182 |
+
# Add a checkbox to enable or disable deep web search functionality in the chat interface
|
|
|
183 |
search_generation = gr.Checkbox(
|
184 |
+
label="Deep Search", # Label the checkbox to indicate its purpose
|
185 |
info=(
|
186 |
+
# Tooltip explaining how to trigger deep search using the /dp command in chat
|
187 |
"Type <i><b>/dp</b></i> followed by the instructions to search the web."
|
188 |
),
|
189 |
+
value=True # Enable deep search by default
|
190 |
)
|
191 |
+
# Create the main chat interface where users can interact with the AI assistant, send messages, and receive responses
|
|
|
192 |
gr.ChatInterface(
|
193 |
+
fn=respond, # Specify the function that processes user input and generates assistant responses
|
194 |
+
# Provide a list of additional input components whose current values are passed to the respond function for each user message
|
195 |
additional_inputs=[
|
196 |
+
model_precision, # Current selected model precision value from the dropdown
|
197 |
+
temperature, # Current temperature value from the slider
|
198 |
+
top_k, # Current top_k value from the slider
|
199 |
+
min_p, # Current min_p value from the slider
|
200 |
+
top_p, # Current top_p value from the slider
|
201 |
+
repetition_penalty, # Current repetition penalty value from the slider
|
202 |
+
reasoning, # Current state of the reasoning checkbox
|
203 |
+
image_generation, # Whether image generation is enabled
|
204 |
+
audio_generation, # Whether audio generation is enabled
|
205 |
+
search_generation # Whether deep search is enabled
|
|
|
206 |
],
|
207 |
+
# Provide a list of example prompts that users can click to quickly test the assistant's capabilities and explore different features
|
208 |
examples=[
|
|
|
209 |
["Please introduce yourself."],
|
210 |
["/audio Could you explain what Artificial Intelligence (AI) is?"],
|
211 |
["/audio What is Hugging Face?"],
|
|
|
220 |
["Please generate a highly complex code snippet on any topic."],
|
221 |
["Explain about quantum computers."]
|
222 |
],
|
223 |
+
cache_examples=False, # Disable caching of example outputs to ensure fresh responses are generated each time an example is selected
|
224 |
chatbot=gr.Chatbot(
|
225 |
+
label="J.A.R.V.I.S.", # Set the title label above the chat window to "J.A.R.V.I.S." for branding and recognition
|
226 |
+
show_copy_button=True, # Display a button that allows users to easily copy chat messages for later use
|
227 |
+
scale=1, # Set the scale factor for the chatbot UI, keeping the default size
|
228 |
+
allow_tags=["think"] # Allow the use of the "think" tag to indicate reasoning mode in chat
|
229 |
),
|
230 |
+
multimodal=False, # Disable file upload capabilities, restricting the chat to text-only interactions
|
231 |
+
api_name="api", # Expose the chat interface as an API endpoint named "api" for programmatic access if needed
|
|
|
|
|
|
|
|
|
|
|
232 |
)
|
233 |
+
# Return the fully constructed Gradio app object, which can be launched as a standalone web application or embedded in other platforms
|
234 |
return app
|
src/utils/session_mapping.py
CHANGED
@@ -3,74 +3,117 @@
|
|
3 |
# SPDX-License-Identifier: Apache-2.0
|
4 |
#
|
5 |
|
6 |
-
import random # Import random module to enable random selection from a list
|
7 |
-
|
8 |
-
from
|
9 |
-
from
|
10 |
-
from
|
11 |
-
from src.utils.helper import busy, mark # Import 'busy' dictionary and 'mark' function to track and update host busy status
|
12 |
|
13 |
-
# Initialize a global dictionary
|
14 |
-
mapping = {} #
|
15 |
|
16 |
-
# Define a function to get an available
|
17 |
def get_host(session_id: str, exclude_hosts: List[str] = None) -> dict:
|
18 |
"""
|
19 |
-
|
|
|
|
|
20 |
|
21 |
Args:
|
22 |
-
session_id (str): A unique identifier
|
|
|
23 |
|
24 |
Returns:
|
25 |
-
dict:
|
26 |
|
27 |
-
|
28 |
-
|
|
|
|
|
29 |
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
It filters out busy hosts and those explicitly excluded, then randomly selects an available host.
|
34 |
-
If no hosts are available, it raises an exception.
|
35 |
-
"""
|
36 |
-
|
37 |
-
# If no list of hosts to exclude is provided, initialize it as an empty list
|
38 |
-
if exclude_hosts is None: # Check if exclude_hosts parameter was omitted or set to None
|
39 |
-
exclude_hosts = [] # Initialize exclude_hosts to an empty list to avoid errors during filtering
|
40 |
-
|
41 |
-
# Check if the session ID already has an assigned host in the mapping dictionary
|
42 |
-
if session_id in mapping: # Verify if a host was previously assigned to this session
|
43 |
-
assigned_host = mapping[session_id] # Retrieve the assigned host dictionary for this session
|
44 |
-
# If the assigned host is not in the list of hosts to exclude, return it immediately
|
45 |
-
if assigned_host["jarvis"] not in exclude_hosts: # Ensure assigned host is allowed for this request
|
46 |
-
return assigned_host # Return the cached host assignment for session consistency
|
47 |
-
else:
|
48 |
-
# If the assigned host is excluded, remove the mapping to allow reassignment
|
49 |
-
del mapping[session_id] # Delete the existing session-host mapping to find a new host
|
50 |
-
|
51 |
-
# Get the current UTC time to compare against host busy status timestamps
|
52 |
-
now = datetime.utcnow() # Capture current time to filter out hosts that are still busy
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
if h["jarvis"] not in busy or busy[h["jarvis"]] <= now # Include hosts not busy or whose busy time has expired
|
58 |
-
if h["jarvis"] not in exclude_hosts # Exclude hosts specified in the exclude_hosts list
|
59 |
-
]
|
60 |
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
# Inform user of service unavailability
|
65 |
-
|
66 |
-
# Randomly select one host from the list of available hosts to distribute load evenly
|
67 |
-
selected = random.choice(available_hosts) # Choose a host at random to avoid bias in host selection
|
68 |
-
|
69 |
-
# Store the selected host in the mapping dictionary for future requests with the same session ID
|
70 |
-
mapping[session_id] = selected # Cache the selected host to maintain session affinity
|
71 |
|
72 |
-
#
|
73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
|
75 |
-
|
76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
# SPDX-License-Identifier: Apache-2.0
|
4 |
#
|
5 |
|
6 |
+
import random # Import the random module to enable random selection of elements from a list, which helps in load balancing host assignments
|
7 |
+
from datetime import datetime # Import the datetime class to work with timestamps, particularly to check and compare current UTC time against host busy status
|
8 |
+
from typing import Dict, List # Import type hinting classes for dictionaries and lists to improve code readability and static analysis, though not explicitly used here
|
9 |
+
from config import auth # Import the 'auth' configuration, which is expected to be a list of host dictionaries containing credentials and identifiers for available hosts
|
10 |
+
from src.utils.helper import busy, mark # Import 'busy', a dictionary tracking host busy states with expiration timestamps, and 'mark', a function to update the busy status of hosts when assigned
|
|
|
11 |
|
12 |
+
# Initialize a global dictionary named 'mapping' that will maintain a persistent association between session IDs and their assigned hosts
|
13 |
+
mapping = {} # This dictionary stores session_id keys mapped to host dictionaries, ensuring consistent host allocation for repeated requests within the same session
|
14 |
|
15 |
+
# Define a function to get an available hosts for a given session
|
16 |
def get_host(session_id: str, exclude_hosts: List[str] = None) -> dict:
|
17 |
"""
|
18 |
+
Obtain a host for the specified session ID, guaranteeing that the function never raises exceptions or returns None.
|
19 |
+
This function implements a robust mechanism to ensure a host is always returned by dynamically excluding busy or failed hosts,
|
20 |
+
and retrying until a suitable host becomes available.
|
21 |
|
22 |
Args:
|
23 |
+
session_id (str): A unique string identifier representing the current user or process session requesting a host.
|
24 |
+
exclude_hosts (List[str], optional): An optional list of host identifiers to be excluded from selection, useful for avoiding problematic or previously failed hosts. Defaults to None.
|
25 |
|
26 |
Returns:
|
27 |
+
dict: A dictionary representing the selected host from the 'auth' configuration, including its credentials and identifier.
|
28 |
|
29 |
+
Detailed Explanation:
|
30 |
+
The function first checks if the session already has an assigned host in the 'mapping' dictionary. If so, it verifies whether
|
31 |
+
the assigned host is neither excluded nor currently busy. If the assigned host is available, it refreshes its busy status to
|
32 |
+
extend its reservation and returns it immediately, ensuring session affinity.
|
33 |
|
34 |
+
If the assigned host is excluded or busy, the mapping is deleted to allow reassignment. The function then filters the list of
|
35 |
+
all hosts from 'auth' to exclude busy hosts, explicitly excluded hosts, and hosts already tried in the current function call.
|
36 |
+
From the filtered list, it randomly selects a host to distribute load evenly.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
|
38 |
+
If no suitable hosts are found, the function updates the set of tried hosts to include all currently busy and excluded hosts,
|
39 |
+
and clears the exclude list to retry all hosts again. This loop continues indefinitely until a host becomes available,
|
40 |
+
ensuring that the function never fails or returns None.
|
|
|
|
|
|
|
41 |
|
42 |
+
This design supports high availability and fault tolerance by dynamically adapting to host availability and avoiding deadlocks
|
43 |
+
or infinite retries on unavailable hosts.
|
44 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
|
46 |
+
# Initialize exclude_hosts as an empty list if no value is provided to avoid errors during host filtering
|
47 |
+
if exclude_hosts is None:
|
48 |
+
exclude_hosts = [] # Assign an empty list to exclude_hosts to safely perform membership checks and list operations later
|
49 |
+
|
50 |
+
# Create a set to track hosts that have been attempted and found unsuitable during this invocation of the function
|
51 |
+
tried_hosts = set() # Using a set for efficient membership testing and to prevent repeated attempts on the same hosts within this call
|
52 |
+
|
53 |
+
# Enter an infinite loop that will only exit when a valid host is found and returned, ensuring robustness and continuous operation
|
54 |
+
while True:
|
55 |
+
# Check if the current session ID already has a host assigned in the mapping dictionary
|
56 |
+
if session_id in mapping:
|
57 |
+
assigned_host = mapping[session_id] # Retrieve the previously assigned host dictionary for this session to maintain session consistency
|
58 |
+
|
59 |
+
# Determine if the assigned host is not in the exclude list and is either not busy or its busy period has expired
|
60 |
+
if (
|
61 |
+
assigned_host["jarvis"] not in exclude_hosts # Confirm the assigned host is not explicitly excluded for this request
|
62 |
+
and (
|
63 |
+
assigned_host["jarvis"] not in busy # Check if the host is not currently marked as busy
|
64 |
+
or busy[assigned_host["jarvis"]] <= datetime.utcnow() # Alternatively, check if the host's busy timestamp has expired, meaning it is free
|
65 |
+
)
|
66 |
+
):
|
67 |
+
# Since the assigned host is available, update its busy timestamp to extend its reservation for this session
|
68 |
+
mark(assigned_host["jarvis"]) # Call the helper function to refresh the busy status, preventing other sessions from using it simultaneously
|
69 |
+
return assigned_host # Return the assigned host dictionary immediately to maintain session affinity and reduce latency
|
70 |
+
|
71 |
+
else:
|
72 |
+
# If the assigned host is either excluded or currently busy, remove the mapping to allow reassignment of a new host
|
73 |
+
del mapping[session_id] # Delete the session-to-host association to enable the selection of a different host in subsequent steps
|
74 |
+
|
75 |
+
# Capture the current UTC time once to use for filtering hosts based on their busy status
|
76 |
+
now = datetime.utcnow() # Store the current time to compare against busy timestamps for all hosts
|
77 |
+
|
78 |
+
# Generate a list of hosts that are eligible for selection by applying multiple filters:
|
79 |
+
# 1. Hosts not currently busy or whose busy period has expired
|
80 |
+
# 2. Hosts not included in the exclude_hosts list provided by the caller
|
81 |
+
# 3. Hosts not already attempted in this function call to avoid redundant retries
|
82 |
+
available_hosts = [
|
83 |
+
h for h in auth # Iterate over all hosts defined in the authentication configuration list
|
84 |
+
if h["jarvis"] not in busy or busy[h["jarvis"]] <= now # Include only hosts that are free or whose busy reservation has expired
|
85 |
+
if h["jarvis"] not in exclude_hosts # Exclude hosts explicitly marked to be avoided for this selection
|
86 |
+
if h["jarvis"] not in tried_hosts # Exclude hosts that have already been tried and failed during this function call to prevent infinite loops
|
87 |
+
]
|
88 |
+
|
89 |
+
# If there are any hosts available after filtering, proceed to select one randomly
|
90 |
+
if available_hosts:
|
91 |
+
selected = random.choice(available_hosts) # Randomly pick one host from the available list to distribute load fairly among hosts
|
92 |
+
|
93 |
+
# Store the selected host in the global mapping dictionary to maintain session affinity for future requests with the same session ID
|
94 |
+
mapping[session_id] = selected # Cache the selected host so subsequent calls with this session ID return the same host
|
95 |
+
|
96 |
+
# Mark the selected host as busy by updating its busy timestamp, indicating it is currently reserved for this session
|
97 |
+
mark(selected["jarvis"]) # Update the busy dictionary to reflect that this host is now occupied, preventing concurrent use
|
98 |
+
|
99 |
+
# Return the selected host dictionary to the caller, completing the host assignment process for the session
|
100 |
+
return selected
|
101 |
|
102 |
+
else:
|
103 |
+
# If no hosts are available that have not already been tried, update the tried_hosts set to include all hosts currently busy or excluded
|
104 |
+
# This prevents immediate reattempts on these hosts in the next iteration, allowing time for busy hosts to become free
|
105 |
+
tried_hosts.update(
|
106 |
+
h["jarvis"] for h in auth # Iterate over all hosts in the auth list to identify those currently busy
|
107 |
+
if h["jarvis"] in busy and busy[h["jarvis"]] > now # Add hosts whose busy timestamp is still in the future, indicating they are occupied
|
108 |
+
)
|
109 |
+
tried_hosts.update(exclude_hosts) # Also add all explicitly excluded hosts to the tried_hosts set to avoid retrying them immediately
|
110 |
+
|
111 |
+
# Create a set of all host identifiers from the auth configuration to compare against tried_hosts
|
112 |
+
all_host_ids = {h["jarvis"] for h in auth} # Extract all host IDs to check if all hosts have been attempted
|
113 |
+
|
114 |
+
# If the set of tried hosts now includes every host in the configuration, clear the tried_hosts set to reset the retry mechanism
|
115 |
+
if tried_hosts >= all_host_ids:
|
116 |
+
tried_hosts.clear() # Clear the tried_hosts set to allow all hosts to be considered again, enabling retries after some time
|
117 |
+
|
118 |
+
# Clear the exclude_hosts list as well to allow all hosts to be eligible for selection in the next iteration
|
119 |
+
exclude_hosts.clear() # Reset the exclude list so that no hosts are excluded in the upcoming retry cycle, maximizing availability
|
src/utils/tools.py
CHANGED
@@ -3,65 +3,85 @@
|
|
3 |
# SPDX-License-Identifier: Apache-2.0
|
4 |
#
|
5 |
|
6 |
-
import random # Import random module to enable random selection
|
7 |
-
import
|
8 |
-
from
|
9 |
-
from
|
10 |
-
from src.utils.helper import busy, mark # Import 'busy' dictionary and 'mark' function to track and update host busy status
|
11 |
|
|
|
12 |
def initialize_tools():
|
13 |
"""
|
14 |
-
|
15 |
|
16 |
-
|
17 |
-
tuple: A tuple containing three elements:
|
18 |
-
- tool_setup (str): The endpoint or configuration for the main tool setup.
|
19 |
-
- image_tool (str): The endpoint URL for image generation services.
|
20 |
-
- audio_tool (str): The endpoint URL for audio generation services.
|
21 |
|
22 |
-
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
-
|
26 |
-
This function filters the list of hosts from 'auth' to find those not currently busy or whose busy period has expired.
|
27 |
-
It randomly selects one available host, marks it as busy for one hour,
|
28 |
-
and retrieves the required tool endpoints ('done', 'image', 'audio') from the selected host's configuration.
|
29 |
-
If any of these endpoints are missing, it raises an exception.
|
30 |
-
Finally, it returns the three tool endpoints as a tuple.
|
31 |
-
"""
|
32 |
-
# Get the current UTC time for busy status comparison
|
33 |
-
now = datetime.utcnow()
|
34 |
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
40 |
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
|
|
|
|
45 |
|
46 |
-
|
47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
|
49 |
-
|
50 |
-
|
51 |
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
|
|
|
|
|
|
|
|
|
|
57 |
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
|
|
|
|
62 |
|
63 |
-
|
64 |
-
|
|
|
|
|
|
|
65 |
|
66 |
-
#
|
67 |
-
tool_setup, image_tool, audio_tool, poll_token = initialize_tools()
|
|
|
|
3 |
# SPDX-License-Identifier: Apache-2.0
|
4 |
#
|
5 |
|
6 |
+
import random # Import the random module to enable functions for random selection and shuffling, which are essential for distributing workload evenly across multiple hosts and avoiding selection bias
|
7 |
+
from datetime import datetime, timedelta # Import datetime and timedelta classes to handle precise time calculations, which are crucial for managing host availability windows and expiration of busy statuses
|
8 |
+
from config import auth # Import the 'auth' configuration list, which contains dictionaries representing hosts with their respective credentials, endpoints, and tokens necessary for tool integration
|
9 |
+
from src.utils.helper import busy, mark # Import 'busy', a dictionary tracking the busy state and expiration timestamps of hosts, and 'mark', a utility function to update this dictionary to indicate when a host has been assigned and for how long
|
|
|
10 |
|
11 |
+
# Define a function to get available tools
|
12 |
def initialize_tools():
|
13 |
"""
|
14 |
+
This function is designed to robustly and perpetually search for an available host from a predefined list of hosts, ensuring that the selected host is fully configured with all required service endpoints and tokens before returning.
|
15 |
|
16 |
+
It operates in an infinite loop to guarantee that it never returns a None value or raises an exception, thereby providing a reliable mechanism for host selection even in dynamic environments where host availability fluctuates frequently.
|
|
|
|
|
|
|
|
|
17 |
|
18 |
+
The detailed workflow is as follows:
|
19 |
+
- Obtain the current UTC timestamp to accurately evaluate the expiration of hosts' busy periods.
|
20 |
+
- Filter the list of configured hosts to include only those that are either not currently marked as busy or whose busy period has elapsed, thus ensuring only eligible hosts are considered for assignment.
|
21 |
+
- If no hosts meet the availability criteria, identify and remove expired busy entries from the tracking dictionary to free up hosts and retry immediately.
|
22 |
+
- Randomly shuffle the filtered list of available hosts to prevent selection bias and promote equitable distribution of workload.
|
23 |
+
- Iterate through the shuffled hosts, verifying that each host possesses all critical configuration elements: the main tool setup endpoint, image generation endpoint, audio generation endpoint, and Pollinations AI API token.
|
24 |
+
- Upon finding a host with complete and valid configurations, mark it as busy for a fixed duration (one hour) to prevent immediate reassignment and potential conflicts.
|
25 |
+
- Return a tuple containing the four key elements from the selected host, enabling downstream components to utilize these endpoints and tokens for their operations.
|
26 |
+
- If no suitable host is found after checking all available hosts, perform a cleanup of expired busy statuses and continue the search without delay.
|
27 |
|
28 |
+
This approach ensures continuous availability of a properly configured host, adapts dynamically to changing host states, and maintains system stability by preventing simultaneous conflicting assignments.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
|
30 |
+
Returns:
|
31 |
+
tuple: A four-element tuple consisting of:
|
32 |
+
- tool_setup (str): The endpoint or configuration string for the primary tool setup service.
|
33 |
+
- image_tool (str): The URL endpoint for the image generation service.
|
34 |
+
- audio_tool (str): The URL endpoint for the audio generation service.
|
35 |
+
- poll_token (str): The API token string required for authenticating with Pollinations AI services.
|
36 |
+
"""
|
37 |
+
while True: # Start an endless loop that only breaks when a fully valid and available host is successfully selected and returned
|
38 |
+
now = datetime.utcnow() # Capture the current coordinated universal time to accurately compare against host busy expiration timestamps
|
39 |
|
40 |
+
# Create a filtered list of hosts that are eligible for assignment
|
41 |
+
# Eligibility criteria include hosts not currently marked as busy or those whose busy period has expired, thereby allowing their reuse
|
42 |
+
available = [
|
43 |
+
item for item in auth # Iterate over each host configuration dictionary in the 'auth' list imported from the configuration module
|
44 |
+
if item["jarvis"] not in busy or busy[item["jarvis"]] <= now # Include the host if it is not present in the busy dictionary or if its busy timestamp is earlier than or equal to the current time, indicating availability
|
45 |
+
]
|
46 |
|
47 |
+
# Check if the filtered list of available hosts is empty, which means all hosts are currently busy with unexpired busy periods
|
48 |
+
if not available:
|
49 |
+
# Identify all hosts in the busy dictionary whose busy periods have expired by comparing their timestamps to the current time
|
50 |
+
expired_hosts = [host for host in busy if busy[host] <= now] # Generate a list of host identifiers whose busy status has expired and are thus candidates for cleanup
|
51 |
+
for host in expired_hosts: # Iterate over each expired host to perform cleanup operations
|
52 |
+
del busy[host] # Remove the host's busy status entry from the dictionary, effectively marking it as available again for assignment
|
53 |
+
# After cleaning up expired busy statuses, continue the loop to reattempt host selection immediately, ensuring responsiveness and minimal delay in availability checks
|
54 |
+
continue
|
55 |
|
56 |
+
# Randomize the order of the available hosts list to prevent any selection bias and promote fair workload distribution among hosts
|
57 |
+
random.shuffle(available)
|
58 |
|
59 |
+
# Iterate through each host in the shuffled list to find one that has all the required endpoints and tokens properly configured
|
60 |
+
for selected in available:
|
61 |
+
# Extract the main tool setup endpoint from the selected host's configuration dictionary using the key 'done'
|
62 |
+
tool_setup = selected.get("done")
|
63 |
+
# Extract the image generation service endpoint URL using the key 'image'
|
64 |
+
image_tool = selected.get("image")
|
65 |
+
# Extract the audio generation service endpoint URL using the key 'audio'
|
66 |
+
audio_tool = selected.get("audio")
|
67 |
+
# Extract the Pollinations AI API token string using the key 'pol'
|
68 |
+
poll_token = selected.get("pol")
|
69 |
|
70 |
+
# Verify that all four critical configuration values are present and non-empty, ensuring the host is fully capable of handling the required services
|
71 |
+
if tool_setup and image_tool and audio_tool and poll_token:
|
72 |
+
# Mark the selected host as busy for the next hour by invoking the 'mark' function with the host's unique identifier
|
73 |
+
# This prevents the same host from being assigned again immediately, allowing for load balancing and avoiding conflicts
|
74 |
+
mark(selected["jarvis"])
|
75 |
+
# Return a tuple containing all four endpoints and the token, signaling successful host selection and configuration retrieval
|
76 |
+
return tool_setup, image_tool, audio_tool, poll_token
|
77 |
+
# If any required endpoint or token is missing, skip this host and continue checking the next one in the list
|
78 |
|
79 |
+
# If none of the available hosts had all required endpoints and tokens, perform cleanup of expired busy statuses again before retrying
|
80 |
+
expired_hosts = [host for host in busy if busy[host] <= now] # Identify hosts whose busy times have expired and are eligible to be freed up
|
81 |
+
for host in expired_hosts:
|
82 |
+
del busy[host] # Remove expired busy entries to free up hosts for reassignment
|
83 |
+
# Continue the loop to retry host selection immediately, maintaining continuous operation without delay
|
84 |
|
85 |
+
# Execute the host initialization process by calling the function to obtain an available host's endpoints and tokens
|
86 |
+
tool_setup, image_tool, audio_tool, poll_token = initialize_tools()
|
87 |
+
# This call blocks until a suitable host is found and returns the necessary configuration for downstream tool usage
|