Settings prototype

Settings modal window managed from python - work in progress

frdel committed Oct 27, 2024 at 18:04 UTC a5d671904d4deebf147adc2b728bdc5b36627144
9 files changed +976 -50
initialize.py
+9 -6
@@ -1,11 +1,11 @@
1 import models
2 from agent import AgentConfig
3 -from python.helpers import files
3 +from python.helpers import files, settings
4
5 def initialize():
6
7 # main chat model used by agents (smarter, more accurate)
8 - chat_llm = models.get_openai_chat(model_name="gpt-4o-mini", temperature=0)
8 + # chat_llm = models.get_openai_chat(model_name="gpt-4o-mini", temperature=0)
9 # chat_llm = models.get_ollama_chat(model_name="llama3.2:3b-instruct-fp16", temperature=0)
10 # chat_llm = models.get_lmstudio_chat(model_name="lmstudio-community/Meta-Llama-3.1-8B-Instruct-GGUF", temperature=0)
11 # chat_llm = models.get_openrouter_chat(model_name="openai/o1-mini-2024-09-12")
@@ -15,22 +15,25 @@ def initialize():
15 # chat_llm = models.get_mistral_chat(model_name="mistral-small-latest", temperature=0)
16 # chat_llm = models.get_groq_chat(model_name="llama-3.2-90b-text-preview", temperature=0)
17 # chat_llm = models.get_sambanova_chat(model_name="Meta-Llama-3.1-70B-Instruct-8k", temperature=0)
18 + chat_llm = settings.get_chat_model() # chat model from user settings
19
20 # utility model used for helper functions (cheaper, faster)
20 - utility_llm = chat_llm
21 + # utility_llm = chat_llm
22 + utility_llm = settings.get_utility_model() # utility model from user settings
23
24 # embedding model used for memory
23 - embedding_llm = models.get_openai_embedding(model_name="text-embedding-3-small")
25 + # embedding_llm = models.get_openai_embedding(model_name="text-embedding-3-small")
26 # embedding_llm = models.get_ollama_embedding(model_name="nomic-embed-text")
27 # embedding_llm = models.get_huggingface_embedding(model_name="sentence-transformers/all-MiniLM-L6-v2")
28 # embedding_llm = models.get_lmstudio_embedding(model_name="nomic-ai/nomic-embed-text-v1.5-GGUF")
27 -
29 + embedding_llm = settings.get_embedding_model() # embedding model from user settings
30 +
31 # agent configuration
32 config = AgentConfig(
33 chat_model = chat_llm,
34 utility_model = utility_llm,
35 embeddings_model = embedding_llm,
33 - # prompts_subdir = "default",
36 + prompts_subdir = "dianoia-xl",
37 # memory_subdir = "",
38 knowledge_subdirs = ["default","custom"],
39 auto_memory_count = 0,
models.py
+202 -42
@@ -1,5 +1,13 @@
1 +from enum import Enum
2 import os
2 -from langchain_openai import ChatOpenAI, OpenAI, OpenAIEmbeddings, AzureChatOpenAI, AzureOpenAIEmbeddings, AzureOpenAI
3 +from langchain_openai import (
4 + ChatOpenAI,
5 + OpenAI,
6 + OpenAIEmbeddings,
7 + AzureChatOpenAI,
8 + AzureOpenAIEmbeddings,
9 + AzureOpenAI,
10 +)
11 from langchain_community.llms.ollama import Ollama
12 from langchain_ollama import ChatOllama
13 from langchain_community.embeddings import OllamaEmbeddings
@@ -17,74 +25,226 @@ load_dotenv()
25 # Configuration
26 DEFAULT_TEMPERATURE = 0.0
27
28 +
29 +class ModelType(Enum):
30 + CHAT = "Chat"
31 + EMBEDDING = "Embedding"
32 +
33 +
34 +class ModelProvider(Enum):
35 + ANTHROPIC = "Anthropic"
36 + HUGGINGFACE = "HuggingFace"
37 + GOOGLE = "Google"
38 + GROQ = "Groq"
39 + LMSTUDIO = "LM Studio"
40 + MISTRALAI = "Mistral AI"
41 + OLLAMA = "Ollama"
42 + OPENAI = "OpenAI"
43 + OPENAI_AZURE = "OpenAI Azure"
44 + OPENROUTER = "OpenRouter"
45 + SAMBANOVA = "Sambanova"
46 +
47 +
48 # Utility function to get API keys from environment variables
49 def get_api_key(service):
22 - return os.getenv(f"API_KEY_{service.upper()}") or os.getenv(f"{service.upper()}_API_KEY")
50 + return os.getenv(f"API_KEY_{service.upper()}") or os.getenv(
51 + f"{service.upper()}_API_KEY"
52 + )
53 +
54 +
55 +def get_model(type: ModelType, provider: ModelProvider, name: str, **kwargs):
56 + fnc_name = f"get_{provider.name.lower()}_{type.name.lower()}" # function name of model getter
57 + model = globals()[fnc_name](name, **kwargs) # call function by name
58 + return model
59
60
61 # Ollama models
26 -def get_ollama_chat(model_name:str, temperature=DEFAULT_TEMPERATURE, base_url=os.getenv("OLLAMA_BASE_URL") or "http://127.0.0.1:11434", num_ctx=8192):
27 - return ChatOllama(model=model_name,temperature=temperature, base_url=base_url, num_ctx=num_ctx)
62 +def get_ollama_chat(
63 + model_name: str,
64 + temperature=DEFAULT_TEMPERATURE,
65 + base_url=os.getenv("OLLAMA_BASE_URL") or "http://127.0.0.1:11434",
66 + num_ctx=8192,
67 + **kwargs,
68 +):
69 + return ChatOllama(
70 + model=model_name,
71 + temperature=temperature,
72 + base_url=base_url,
73 + num_ctx=num_ctx,
74 + **kwargs,
75 + )
76 +
77 +
78 +def get_ollama_embedding(
79 + model_name: str,
80 + temperature=DEFAULT_TEMPERATURE,
81 + base_url=os.getenv("OLLAMA_BASE_URL") or "http://127.0.0.1:11434",
82 + **kwargs,
83 +):
84 + return OllamaEmbeddings(
85 + model=model_name, temperature=temperature, base_url=base_url, **kwargs
86 + )
87
29 -def get_ollama_embedding(model_name:str, temperature=DEFAULT_TEMPERATURE, base_url=os.getenv("OLLAMA_BASE_URL") or "http://127.0.0.1:11434"):
30 -
31 - return OllamaEmbeddings(model=model_name,temperature=temperature, base_url=base_url)
88
89 # HuggingFace models
90
35 -def get_huggingface_embedding(model_name:str):
36 - return HuggingFaceEmbeddings(model_name=model_name)
91 +
92 +def get_huggingface_embedding(model_name: str, **kwargs):
93 + return HuggingFaceEmbeddings(model_name=model_name, **kwargs)
94 +
95
96 # LM Studio and other OpenAI compatible interfaces
39 -def get_lmstudio_chat(model_name:str, temperature=DEFAULT_TEMPERATURE, base_url=os.getenv("LM_STUDIO_BASE_URL") or "http://127.0.0.1:1234/v1"):
40 - return ChatOpenAI(model_name=model_name, base_url=base_url, temperature=temperature, api_key="none") # type: ignore
97 +def get_lmstudio_chat(
98 + model_name: str,
99 + temperature=DEFAULT_TEMPERATURE,
100 + base_url=os.getenv("LM_STUDIO_BASE_URL") or "http://127.0.0.1:1234/v1",
101 + **kwargs,
102 +):
103 + return ChatOpenAI(model_name=model_name, base_url=base_url, temperature=temperature, api_key="none", **kwargs) # type: ignore
104 +
105 +
106 +def get_lmstudio_embedding(
107 + model_name: str,
108 + base_url=os.getenv("LM_STUDIO_BASE_URL") or "http://127.0.0.1:1234/v1",
109 + **kwargs,
110 +):
111 + return OpenAIEmbeddings(model=model_name, api_key="none", base_url=base_url, check_embedding_ctx_length=False, **kwargs) # type: ignore
112
42 -def get_lmstudio_embedding(model_name:str, base_url=os.getenv("LM_STUDIO_BASE_URL") or "http://127.0.0.1:1234/v1"):
43 - return OpenAIEmbeddings(model=model_name, api_key="none", base_url=base_url, check_embedding_ctx_length=False) # type: ignore
113
114 # Anthropic models
46 -def get_anthropic_chat(model_name:str, api_key=get_api_key("anthropic"), temperature=DEFAULT_TEMPERATURE):
47 - return ChatAnthropic(model_name=model_name, temperature=temperature, api_key=api_key) # type: ignore
115 +def get_anthropic_chat(
116 + model_name: str,
117 + api_key=get_api_key("anthropic"),
118 + temperature=DEFAULT_TEMPERATURE,
119 + **kwargs,
120 +):
121 + return ChatAnthropic(model_name=model_name, temperature=temperature, api_key=api_key, **kwargs) # type: ignore
122 +
123
124 # OpenAI models
50 -def get_openai_chat(model_name:str, api_key=get_api_key("openai"), temperature=DEFAULT_TEMPERATURE):
51 - return ChatOpenAI(model_name=model_name, temperature=temperature, api_key=api_key) # type: ignore
125 +def get_openai_chat(
126 + model_name: str,
127 + api_key=get_api_key("openai"),
128 + temperature=DEFAULT_TEMPERATURE,
129 + **kwargs,
130 +):
131 + return ChatOpenAI(model_name=model_name, temperature=temperature, api_key=api_key, **kwargs) # type: ignore
132 +
133
53 -def get_openai_instruct(model_name:str, api_key=get_api_key("openai"), temperature=DEFAULT_TEMPERATURE):
54 - return OpenAI(model=model_name, temperature=temperature, api_key=api_key) # type: ignore
134 +def get_openai_instruct(
135 + model_name: str,
136 + api_key=get_api_key("openai"),
137 + temperature=DEFAULT_TEMPERATURE,
138 + **kwargs,
139 +):
140 + return OpenAI(model=model_name, temperature=temperature, api_key=api_key, **kwargs) # type: ignore
141
56 -def get_openai_embedding(model_name:str, api_key=get_api_key("openai")):
57 - return OpenAIEmbeddings(model=model_name, api_key=api_key) # type: ignore
142
59 -def get_azure_openai_chat(deployment_name:str, api_key=get_api_key("openai_azure"), temperature=DEFAULT_TEMPERATURE, azure_endpoint=os.getenv("OPENAI_AZURE_ENDPOINT")):
60 - return AzureChatOpenAI(deployment_name=deployment_name, temperature=temperature, api_key=api_key, azure_endpoint=azure_endpoint) # type: ignore
143 +def get_openai_embedding(model_name: str, api_key=get_api_key("openai"), **kwargs):
144 + return OpenAIEmbeddings(model=model_name, api_key=api_key, **kwargs) # type: ignore
145
62 -def get_azure_openai_instruct(deployment_name:str, api_key=get_api_key("openai_azure"), temperature=DEFAULT_TEMPERATURE, azure_endpoint=os.getenv("OPENAI_AZURE_ENDPOINT")):
63 - return AzureOpenAI(deployment_name=deployment_name, temperature=temperature, api_key=api_key, azure_endpoint=azure_endpoint) # type: ignore
146
65 -def get_azure_openai_embedding(deployment_name:str, api_key=get_api_key("openai_azure"), azure_endpoint=os.getenv("OPENAI_AZURE_ENDPOINT")):
66 - return AzureOpenAIEmbeddings(deployment_name=deployment_name, api_key=api_key, azure_endpoint=azure_endpoint) # type: ignore
147 +def get_azure_openai_chat(
148 + deployment_name: str,
149 + api_key=get_api_key("openai_azure"),
150 + temperature=DEFAULT_TEMPERATURE,
151 + azure_endpoint=os.getenv("OPENAI_AZURE_ENDPOINT"),
152 + **kwargs,
153 +):
154 + return AzureChatOpenAI(deployment_name=deployment_name, temperature=temperature, api_key=api_key, azure_endpoint=azure_endpoint, **kwargs) # type: ignore
155 +
156 +
157 +def get_azure_openai_instruct(
158 + deployment_name: str,
159 + api_key=get_api_key("openai_azure"),
160 + temperature=DEFAULT_TEMPERATURE,
161 + azure_endpoint=os.getenv("OPENAI_AZURE_ENDPOINT"),
162 + **kwargs,
163 +):
164 + return AzureOpenAI(deployment_name=deployment_name, temperature=temperature, api_key=api_key, azure_endpoint=azure_endpoint, **kwargs) # type: ignore
165 +
166 +
167 +def get_azure_openai_embedding(
168 + deployment_name: str,
169 + api_key=get_api_key("openai_azure"),
170 + azure_endpoint=os.getenv("OPENAI_AZURE_ENDPOINT"),
171 + **kwargs,
172 +):
173 + return AzureOpenAIEmbeddings(deployment_name=deployment_name, api_key=api_key, azure_endpoint=azure_endpoint, **kwargs) # type: ignore
174 +
175
176 # Google models
69 -def get_google_chat(model_name:str, api_key=get_api_key("google"), temperature=DEFAULT_TEMPERATURE):
70 - return GoogleGenerativeAI(model=model_name, temperature=temperature, google_api_key=api_key, safety_settings={HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE }) # type: ignore
177 +def get_google_chat(
178 + model_name: str,
179 + api_key=get_api_key("google"),
180 + temperature=DEFAULT_TEMPERATURE,
181 + **kwargs,
182 +):
183 + return GoogleGenerativeAI(model=model_name, temperature=temperature, google_api_key=api_key, safety_settings={HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE}, **kwargs) # type: ignore
184 +
185
186 # Mistral models
73 -def get_mistral_chat(model_name:str, api_key=get_api_key("mistral"), temperature=DEFAULT_TEMPERATURE):
74 - return ChatMistralAI(model=model_name, temperature=temperature, api_key=api_key) # type: ignore
187 +def get_mistral_chat(
188 + model_name: str,
189 + api_key=get_api_key("mistral"),
190 + temperature=DEFAULT_TEMPERATURE,
191 + **kwargs,
192 +):
193 + return ChatMistralAI(model=model_name, temperature=temperature, api_key=api_key, **kwargs) # type: ignore
194 +
195
196 # Groq models
77 -def get_groq_chat(model_name:str, api_key=get_api_key("groq"), temperature=DEFAULT_TEMPERATURE):
78 - return ChatGroq(model_name=model_name, temperature=temperature, api_key=api_key) # type: ignore
79 -
197 +def get_groq_chat(
198 + model_name: str,
199 + api_key=get_api_key("groq"),
200 + temperature=DEFAULT_TEMPERATURE,
201 + **kwargs,
202 +):
203 + return ChatGroq(model_name=model_name, temperature=temperature, api_key=api_key, **kwargs) # type: ignore
204 +
205 +
206 # OpenRouter models
81 -def get_openrouter_chat(model_name: str, api_key=get_api_key("openrouter"), temperature=DEFAULT_TEMPERATURE, base_url=os.getenv("OPEN_ROUTER_BASE_URL") or "https://openrouter.ai/api/v1"):
82 - return ChatOpenAI(api_key=api_key, model=model_name, temperature=temperature, base_url=base_url) # type: ignore
83 -
84 -def get_openrouter_embedding(model_name: str, api_key=get_api_key("openrouter"), base_url=os.getenv("OPEN_ROUTER_BASE_URL") or "https://openrouter.ai/api/v1"):
85 - return OpenAIEmbeddings(model=model_name, api_key=api_key, base_url=base_url) # type: ignore
207 +def get_openrouter_chat(
208 + model_name: str,
209 + api_key=get_api_key("openrouter"),
210 + temperature=DEFAULT_TEMPERATURE,
211 + base_url=os.getenv("OPEN_ROUTER_BASE_URL") or "https://openrouter.ai/api/v1",
212 + **kwargs,
213 +):
214 + return ChatOpenAI(api_key=api_key, model=model_name, temperature=temperature, base_url=base_url, **kwargs) # type: ignore
215 +
216 +
217 +def get_openrouter_embedding(
218 + model_name: str,
219 + api_key=get_api_key("openrouter"),
220 + base_url=os.getenv("OPEN_ROUTER_BASE_URL") or "https://openrouter.ai/api/v1",
221 + **kwargs,
222 +):
223 + return OpenAIEmbeddings(model=model_name, api_key=api_key, base_url=base_url, **kwargs) # type: ignore
224 +
225
226 # Sambanova models
88 -def get_sambanova_chat(model_name: str, api_key=get_api_key("sambanova"), temperature=DEFAULT_TEMPERATURE, base_url=os.getenv("SAMBANOVA_BASE_URL") or "https://fast-api.snova.ai/v1", max_tokens=1024):
89 - return ChatOpenAI(api_key=api_key, model=model_name, temperature=temperature, base_url=base_url, max_tokens=max_tokens) # type: ignore
90 -
227 +def get_sambanova_chat(
228 + model_name: str,
229 + api_key=get_api_key("sambanova"),
230 + temperature=DEFAULT_TEMPERATURE,
231 + base_url=os.getenv("SAMBANOVA_BASE_URL") or "https://fast-api.snova.ai/v1",
232 + max_tokens=1024,
233 + **kwargs,
234 +):
235 + return ChatOpenAI(api_key=api_key, model=model_name, temperature=temperature, base_url=base_url, max_tokens=max_tokens, **kwargs) # type: ignore
236 +
237 +
238 +# Other OpenAI compatible models
239 +def get_other_chat(
240 + model_name: str,
241 + api_key=None,
242 + temperature=DEFAULT_TEMPERATURE,
243 + base_url=None,
244 + **kwargs,
245 +):
246 + return ChatOpenAI(api_key=api_key, model=model_name, temperature=temperature, base_url=base_url, **kwargs) # type: ignore
247 +
248 +
249 +def get_other_embedding(model_name: str, api_key=None, base_url=None, **kwargs):
250 + return OpenAIEmbeddings(model=model_name, api_key=api_key, base_url=base_url, **kwargs) # type: ignore
python/helpers/settings.py new
+273
@@ -0,0 +1,273 @@
1 +import json
2 +import os
3 +import re
4 +from typing import Any, Optional, TypedDict
5 +from . import files
6 +from models import get_model, ModelProvider, ModelType
7 +from langchain_core.language_models.chat_models import BaseChatModel
8 +from langchain_core.embeddings import Embeddings
9 +
10 +class Settings(TypedDict):
11 + chat_model_provider: str
12 + chat_model_name: str
13 + chat_model_temperature: float
14 + chat_model_kwargs: dict[str, str]
15 +
16 + util_model_provider: str
17 + util_model_name: str
18 + util_model_temperature: float
19 + util_model_kwargs: dict[str, str]
20 +
21 + embed_model_provider: str
22 + embed_model_name: str
23 + embed_model_temperature: float
24 + embed_model_kwargs: dict[str, str]
25 +
26 +
27 +class PartialSettings(Settings, total=False):
28 + pass
29 +
30 +
31 +SETTINGS_FILE = files.get_abs_path("tmp/settings.json")
32 +_settings: Settings | None = None
33 +
34 +
35 +def convert_out(settings: Settings) -> dict[str, Any]:
36 +
37 + # main model section
38 + chat_model_fields = []
39 + chat_model_fields.append(
40 + {
41 + "id": "chat_model_provider",
42 + "title": "Chat model provider",
43 + "description": "Select provider for main chat model used by Agent Zero",
44 + "type": "select",
45 + "value": settings["chat_model_provider"],
46 + "options": [{"value": p.name, "label": p.value} for p in ModelProvider],
47 + }
48 + )
49 + chat_model_fields.append(
50 + {
51 + "id": "chat_model_name",
52 + "title": "Chat model name",
53 + "description": "Exact name of model from selected provider",
54 + "type": "input",
55 + "value": settings["chat_model_name"],
56 + }
57 + )
58 +
59 + chat_model_fields.append(
60 + {
61 + "id": "chat_model_temperature",
62 + "title": "Chat model temperature",
63 + "description": "Determines the randomness of generated responses. 0 is deterministic, 1 is random",
64 + "type": "range",
65 + "min": 0,
66 + "max": 1,
67 + "step": 0.01,
68 + "value": settings["chat_model_temperature"],
69 + }
70 + )
71 +
72 + chat_model_fields.append(
73 + {
74 + "id": "chat_model_kwargs",
75 + "title": "Chat model additional parameters",
76 + "description": "Any other parameters supported by the model. Format is KEY=VALUE on individual lines, just like .env file.",
77 + "type": "textarea",
78 + "value": _dict_to_env(settings["chat_model_kwargs"]),
79 + }
80 + )
81 +
82 + chat_model_seciton = {
83 + "title": "Chat Model",
84 + "description": "Selection and settings for main chat model used by Agent Zero",
85 + "fields": chat_model_fields,
86 + }
87 +
88 + # main model section
89 + util_model_fields = []
90 + util_model_fields.append(
91 + {
92 + "id": "util_model_provider",
93 + "title": "Utility model provider",
94 + "description": "Select provider for utility model used by the framework",
95 + "type": "select",
96 + "value": settings["util_model_provider"],
97 + "options": [{"value": p.name, "label": p.value} for p in ModelProvider],
98 + }
99 + )
100 + util_model_fields.append(
101 + {
102 + "id": "util_model_name",
103 + "title": "Utility model name",
104 + "description": "Exact name of model from selected provider",
105 + "type": "input",
106 + "value": settings["util_model_name"],
107 + }
108 + )
109 +
110 + util_model_fields.append(
111 + {
112 + "id": "util_model_temperature",
113 + "title": "Utility model temperature",
114 + "description": "Determines the randomness of generated responses. 0 is deterministic, 1 is random",
115 + "type": "range",
116 + "min": 0,
117 + "max": 1,
118 + "step": 0.01,
119 + "value": settings["util_model_temperature"],
120 + }
121 + )
122 +
123 + util_model_fields.append(
124 + {
125 + "id": "util_model_kwargs",
126 + "title": "Utility model additional parameters",
127 + "description": "Any other parameters supported by the model. Format is KEY=VALUE on individual lines, just like .env file.",
128 + "type": "textarea",
129 + "value": _dict_to_env(settings["util_model_kwargs"]),
130 + }
131 + )
132 +
133 + util_model_seciton = {
134 + "title": "Utility model",
135 + "description": "Smaller, cheaper, faster model for handling utility tasks like organizing memory, preparing prompts, summarizing.",
136 + "fields": util_model_fields,
137 + }
138 +
139 + result = {"sections": [chat_model_seciton, util_model_seciton]}
140 + return result
141 +
142 +def convert_in(settings: dict[str, Any]) -> Settings:
143 + current = get_settings()
144 + for section in settings["sections"]:
145 + for field in section["fields"]:
146 + if field["id"].endswith("_kwargs"):
147 + current[field["id"]] = _env_to_dict(field["value"]) #parse KWARGS from env format
148 + else:
149 + current[field["id"]] = field["value"]
150 + return current
151 +
152 +
153 +def get_settings() -> Settings:
154 + global _settings
155 + if not _settings:
156 + _settings = _read_settings_file()
157 + if not _settings:
158 + _settings = _get_default_settings()
159 + return _settings.copy()
160 +
161 +
162 +def set_settings(settings: Settings):
163 + global _settings
164 + _settings = normalize_settings(settings)
165 + _apply_settings()
166 + _write_settings_file(_settings)
167 +
168 +
169 +def normalize_settings(settings: Settings) -> Settings:
170 + copy = settings.copy()
171 + default = _get_default_settings()
172 + for key, value in default.items():
173 + if key not in copy:
174 + copy[key] = value
175 + return copy
176 +
177 +
178 +def get_chat_model() -> BaseChatModel:
179 + settings = get_settings()
180 + return get_model(
181 + type=ModelType.CHAT,
182 + provider=ModelProvider[settings["chat_model_provider"]],
183 + name=settings["chat_model_name"],
184 + temperature=settings["chat_model_temperature"],
185 + **settings["chat_model_kwargs"],
186 + )
187 +
188 +
189 +def get_utility_model() -> BaseChatModel:
190 + settings = get_settings()
191 + return get_model(
192 + type=ModelType.CHAT,
193 + provider=ModelProvider[settings["util_model_provider"]],
194 + name=settings["util_model_name"],
195 + temperature=settings["util_model_temperature"],
196 + **settings["util_model_kwargs"],
197 + )
198 +
199 +
200 +def get_embedding_model() -> Embeddings:
201 + settings = get_settings()
202 + return get_model(
203 + type=ModelType.EMBEDDING,
204 + provider=ModelProvider[settings["embed_model_provider"]],
205 + name=settings["embed_model_name"],
206 + temperature=settings["embed_model_temperature"],
207 + **settings["embed_model_kwargs"],
208 + )
209 +
210 +
211 +def _read_settings_file() -> Settings | None:
212 + if os.path.exists(SETTINGS_FILE):
213 + content = files.read_file(SETTINGS_FILE)
214 + parsed = json.loads(content)
215 + return normalize_settings(parsed)
216 +
217 +
218 +def _write_settings_file(settings: Settings):
219 + content = json.dumps(settings, indent=4)
220 + files.write_file(SETTINGS_FILE, content)
221 +
222 +
223 +def _get_default_settings() -> Settings:
224 + return Settings(
225 + chat_model_provider=ModelProvider.OPENAI.name,
226 + chat_model_name="gpt-4o-mini",
227 + chat_model_temperature=0,
228 + chat_model_kwargs={},
229 + util_model_provider=ModelProvider.OPENAI.name,
230 + util_model_name="gpt-4o-mini",
231 + util_model_temperature=0,
232 + util_model_kwargs={},
233 + embed_model_provider=ModelProvider.OPENAI.name,
234 + embed_model_name="text-embedding-3-small",
235 + embed_model_temperature=0,
236 + embed_model_kwargs={},
237 + )
238 +
239 +def _apply_settings():
240 + global _settings
241 + if _settings:
242 + from agent import AgentContext
243 + from initialize import initialize
244 +
245 + for ctx in AgentContext._contexts.values():
246 + ctx.config = initialize() # reinitialize context config with new settings
247 + #apply config to agents
248 + agent = ctx.agent0
249 + while agent:
250 + agent.config = ctx.config
251 + agent = agent.get_data("subordinate")
252 +
253 +def _env_to_dict(data:str):
254 + env_dict = {}
255 + line_pattern = re.compile(r'\s*([^#][^=]*)\s*=\s*(.*)')
256 + for line in data.splitlines():
257 + match = line_pattern.match(line)
258 + if match:
259 + key, value = match.groups()
260 + # Remove optional surrounding quotes (single or double)
261 + value = value.strip().strip('"').strip("'")
262 + env_dict[key.strip()] = value
263 + return env_dict
264 +
265 +def _dict_to_env(data_dict):
266 + lines = []
267 + for key, value in data_dict.items():
268 + if '\n' in value:
269 + value = f"'{value}'"
270 + elif ' ' in value or value == '' or any(c in value for c in '"\''):
271 + value = f'"{value}"'
272 + lines.append(f"{key}={value}")
273 + return "\n".join(lines)
\ No newline at end of file
python/helpers/whisper_oai.py new
+13
@@ -0,0 +1,13 @@
1 +# Import the necessary libraries
2 +import whisper
3 +import files
4 +
5 +# Load the base model from Whisper
6 +model = whisper.load_model("base")
7 +
8 +# Add your Audio File
9 +audio = files.get_abs_path("audio.ogg")
10 +
11 +# Transcribe the audio file
12 +result = model.transcribe(audio, fp16=False)
13 +print(result["text"])
\ No newline at end of file
run_ui.py
+46 -1
@@ -12,7 +12,7 @@ from python.helpers import files
12 from python.helpers.files import get_abs_path
13 from python.helpers.print_style import PrintStyle
14 from python.helpers.dotenv import load_dotenv
15 -from python.helpers import persist_chat
15 +from python.helpers import persist_chat, settings
16
17
18 # initialize the internal Flask server
@@ -348,6 +348,51 @@ async def poll():
348 # return jsonify(response)
349
350
351 +# get current settings
352 +@app.route("/getSettings", methods=["POST"])
353 +async def get_settings():
354 + try:
355 +
356 + # data sent to the server
357 + input = request.get_json()
358 +
359 + set = settings.convert_out(settings.get_settings())
360 +
361 + response = {"ok": True, "settings": set}
362 +
363 + except Exception as e:
364 + response = {
365 + "ok": False,
366 + "message": str(e),
367 + }
368 + PrintStyle.error(str(e))
369 +
370 + # respond with json
371 + return jsonify(response)
372 +
373 +# set current settings
374 +@app.route("/setSettings", methods=["POST"])
375 +async def set_settings():
376 + try:
377 +
378 + # data sent to the server
379 + input = request.get_json()
380 +
381 + set = settings.convert_in(input)
382 + set = settings.set_settings(set)
383 +
384 + response = {"ok": True, "settings": set}
385 +
386 + except Exception as e:
387 + response = {
388 + "ok": False,
389 + "message": str(e),
390 + }
391 + PrintStyle.error(str(e))
392 +
393 + # respond with json
394 + return jsonify(response)
395 +
396 def run():
397 print("Initializing framework...")
398
webui/index.html
+114
@@ -7,6 +7,7 @@
7 <title>Agent Zero</title>
8 <link rel="stylesheet" href="index.css">
9 <link rel="stylesheet" href="toast.css">
10 + <link rel="stylesheet" href="settings.css">
11
12 <script>
13 window.safeCall = function (name, ...args) {
@@ -15,6 +16,7 @@
16 </script>
17 <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
18 <script type="module" src="index.js"></script>
19 + <script type="text/javascript" src="settings.js"></script>
20
21 </head>
22
@@ -47,6 +49,8 @@
49 <button class="config-button" id="newChat" @click="newChat()">New Chat</button>
50 <button class="config-button" id="loadChats" @click="loadChats()">Load Chat</button>
51 <button class="config-button" id="loadChat" @click="saveChat()">Save Chat</button>
52 + <button class="config-button" id="settings"
53 + @click="settingsModalProxy.openModal()">Settings</button>
54 </div>
55
56 <div class="config-section" id="chats-section" x-data="{ contexts: [], selected: '' }"
@@ -153,6 +157,116 @@
157 </div>
158 </div>
159 </div>
160 +
161 + <div id="settingsModal" x-data="settingsModalProxy">
162 + <h1 x-text="settings.title"></h1>
163 + <template x-teleport="body">
164 + <div x-show="isOpen" class="modal-overlay" @click.self="handleCancel()"
165 + x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0"
166 + x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-200"
167 + x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0">
168 + <div class="modal-container">
169 + <div class="modal-header">
170 + <h2 x-text="settings.title"></h2>
171 + <!-- Dynamically generated navigation -->
172 + <nav>
173 + <ul>
174 + <!-- Loop over sections to generate links dynamically -->
175 + <template x-for="(section, index) in settings.sections" :key="index">
176 + <li>
177 + <a :href="'#section' + (index + 1)" x-text="section.title"></a>
178 + </li>
179 + </template>
180 + </ul>
181 + </nav>
182 + </div>
183 +
184 + <div class="modal-content">
185 + <template x-for="(section, sectionIndex) in settings.sections" :key="sectionIndex">
186 + <div :id="'section' + (sectionIndex + 1)" class="section">
187 + <div class="section-title" x-text="section.title"></div>
188 + <div class="section-description" x-text="section.description"></div>
189 +
190 + <template x-for="(field, fieldIndex) in section.fields" :key="fieldIndex">
191 + <div :class="{'field': true, 'field-full': field.type === 'textarea'}">
192 + <div class="field-label">
193 + <div class="field-title" x-text="field.title"></div>
194 + <div class="field-description" x-text="field.description"></div>
195 + </div>
196 +
197 + <div class="field-control">
198 + <!-- Input field -->
199 + <template x-if="field.type === 'input'">
200 + <input type="text" :class="field.classes" :value="field.value"
201 + :readonly="field.readonly === true"
202 + @input="field.value = $event.target.value">
203 + </template>
204 +
205 + <!-- Textarea field -->
206 + <template x-if="field.type === 'textarea'">
207 + <textarea :class="field.classes" :value="field.value"
208 + :readonly="field.readonly === true"
209 + @input="field.value = $event.target.value"></textarea>
210 + </template>
211 +
212 + <!-- Switch field -->
213 + <template x-if="field.type === 'switch'">
214 + <label class="toggle">
215 + <input type="checkbox" :checked="field.value"
216 + :disabled="field.readonly === true"
217 + @change="field.value = $event.target.checked">
218 + <span class="toggler"></span>
219 + </label>
220 + </template>
221 +
222 + <!-- Range field -->
223 + <template x-if="field.type === 'range'">
224 + <div class="field-control">
225 + <input type="range" :min="field.min" :max="field.max"
226 + :step="field.step" :value="field.value"
227 + :disabled="field.readonly === true"
228 + @input="field.value = $event.target.value"
229 + :class="field.classes">
230 + <span class="range-value" x-text="field.value"></span>
231 + </div>
232 + </template>
233 +
234 + <!-- Button field -->
235 + <template x-if="field.type === 'button'">
236 + <button class="btn btn-field" :class="field.classes"
237 + :disabled="field.readonly === true"
238 + @click="handleFieldButton(field)" x-text="field.value"></button>
239 + </template>
240 +
241 + <!-- Select field -->
242 + <template x-if="field.type === 'select'">
243 + <select :class="field.classes" x-model="field.value"
244 + :disabled="field.readonly === true">
245 + <template x-for="option in field.options" :key="option.value">
246 + <option :value="option.value" x-text="option.label"
247 + :selected="option.value === field.value"></option>
248 + </template>
249 + </select>
250 + </template>
251 + </div>
252 + </div>
253 + </template>
254 + </div>
255 + </template>
256 + </div>
257 +
258 + <div class="modal-footer">
259 + <template x-for="button in settings.buttons" :key="button.id">
260 + <button :class="button.classes" @click="handleButton(button.id)"
261 + x-text="button.title"></button>
262 + </template>
263 + </div>
264 + </div>
265 + </div>
266 + </template>
267 + </div>
268 +
269 +
270 </body>
271
272 </html>
\ No newline at end of file
webui/index.js
+1 -1
@@ -153,7 +153,7 @@ function adjustTextareaHeight() {
153 chatInput.style.height = (chatInput.scrollHeight) + 'px';
154 }
155
156 -async function sendJsonData(url, data) {
156 +window.sendJsonData = async function (url, data) {
157 const response = await fetch(url, {
158 method: 'POST',
159 headers: {
webui/settings.css new
+231
@@ -0,0 +1,231 @@
1 +.modal-overlay {
2 + position: fixed;
3 + top: 0;
4 + left: 0;
5 + right: 0;
6 + bottom: 0;
7 + background: rgba(0, 0, 0, 0.5);
8 + display: flex;
9 + align-items: center;
10 + justify-content: center;
11 +}
12 +
13 +.modal-container {
14 + background: var(--color-panel);
15 + color: var(--color-primary);
16 + border-radius: 0.5rem;
17 + width: 90%;
18 + max-width: 800px;
19 + max-height: 90vh;
20 + display: flex;
21 + flex-direction: column;
22 +}
23 +
24 +.modal-header {
25 + padding: 1.5rem 2rem;
26 + border-bottom: 1px solid #eee;
27 +}
28 +
29 +.modal-content {
30 + padding: 2rem;
31 + overflow-y: auto;
32 + flex-grow: 1;
33 +}
34 +
35 +.modal-footer {
36 + padding: 1.5rem 2rem;
37 + border-top: 1px solid #eee;
38 + display: flex;
39 + justify-content: flex-end;
40 + gap: 1rem;
41 + background: var(--color-background);
42 +}
43 +
44 +.section {
45 + margin-bottom: 2rem;
46 + padding: 1rem;
47 + border: 1px solid #eee;
48 + border-radius: 0.5rem;
49 +}
50 +
51 +.section-title {
52 + font-size: 1.25rem;
53 + font-weight: bold;
54 + margin-bottom: 0.5rem;
55 +}
56 +
57 +.section-description {
58 + color: #666;
59 + margin-bottom: 1rem;
60 +}
61 +
62 +.field {
63 + margin-bottom: 1.5rem;
64 + display: grid;
65 + grid-template-columns: 200px 1fr;
66 + gap: 1rem;
67 + align-items: center;
68 +}
69 +
70 +.field.field-full {
71 + grid-template-columns: 1fr;
72 +}
73 +
74 +.field-label {
75 + display: flex;
76 + flex-direction: column;
77 +}
78 +
79 +.field-title {
80 + font-weight: bold;
81 +}
82 +
83 +.field-description {
84 + color: #666;
85 + font-size: 0.875rem;
86 + margin-top: 0.25rem;
87 +}
88 +
89 +.field-control {
90 + display: flex;
91 + align-items: center;
92 + gap: 0.5rem;
93 +}
94 +
95 +input[type="text"] {
96 + width: 100%;
97 + padding: 0.5rem;
98 + border: 1px solid #ddd;
99 + border-radius: 0.25rem;
100 +}
101 +
102 +textarea {
103 + width: 100%;
104 + min-height: 100px;
105 + padding: 0.5rem;
106 + border: 1px solid #ddd;
107 + border-radius: 0.25rem;
108 + margin-top: 1rem;
109 + font-family: inherit;
110 + resize: vertical;
111 +}
112 +
113 +.toggle {
114 + position: relative;
115 + display: inline-block;
116 + width: 60px;
117 + height: 34px;
118 + margin: 0;
119 +}
120 +
121 +.toggle input {
122 + opacity: 0;
123 + width: 0;
124 + height: 0;
125 +}
126 +
127 +.toggler {
128 + position: absolute;
129 + cursor: pointer;
130 + top: 0;
131 + left: 0;
132 + right: 0;
133 + bottom: 0;
134 + background-color: #ccc;
135 + transition: .4s;
136 + border-radius: 34px;
137 +}
138 +
139 +.toggler:before {
140 + position: absolute;
141 + content: "";
142 + height: 26px;
143 + width: 26px;
144 + left: 4px;
145 + bottom: 4px;
146 + background-color: white;
147 + transition: .4s;
148 + border-radius: 50%;
149 +}
150 +
151 +input:checked+.toggler {
152 + background-color: #2196F3;
153 +}
154 +
155 +input:checked+.toggler:before {
156 + transform: translateX(26px);
157 +}
158 +
159 +input[type="range"] {
160 + width: 100%;
161 +}
162 +
163 +.range-value {
164 + min-width: 3em;
165 + text-align: right;
166 +}
167 +
168 +.btn {
169 + padding: 0.5rem 1rem;
170 + border-radius: 0.25rem;
171 + cursor: pointer;
172 + border: none;
173 + font-size: 0.875rem;
174 +}
175 +
176 +.btn-ok {
177 + background: #2196F3;
178 + color: white;
179 +}
180 +
181 +.btn-cancel {
182 + background: #ddd;
183 + color: #333;
184 +}
185 +
186 +.btn-field {
187 + background: #2196F3;
188 + color: white;
189 + width: fit-content;
190 +}
191 +
192 +.btn-field:disabled {
193 + background: #ccc;
194 + cursor: not-allowed;
195 +}
196 +
197 +select {
198 + width: 100%;
199 + padding: 0.5rem;
200 + border: 1px solid #ddd;
201 + border-radius: 0.25rem;
202 + background-color: white;
203 + font-size: inherit;
204 + cursor: pointer;
205 +}
206 +
207 +select:disabled {
208 + background-color: #f5f5f5;
209 + cursor: not-allowed;
210 +}
211 +
212 +/* Style for navigation links */
213 +nav ul {
214 + list-style-type: none;
215 + padding: 0;
216 +}
217 +
218 +nav ul li {
219 + display: inline;
220 + margin-right: 1rem;
221 +}
222 +
223 +nav ul li a {
224 + text-decoration: none;
225 + color: #2196F3;
226 + font-weight: bold;
227 +}
228 +
229 +nav ul li a:hover {
230 + text-decoration: underline;
231 +}
\ No newline at end of file
webui/settings.js new
+87
@@ -0,0 +1,87 @@
1 +const settingsModalProxy = {
2 + isOpen: false,
3 + settings: {},
4 + resolvePromise: null,
5 +
6 +
7 + async openModal() {
8 +
9 + const modalEl = document.getElementById('settingsModal');
10 + const modalAD = Alpine.$data(modalEl);
11 +
12 + //get settings from backend
13 + const set = await sendJsonData("/getSettings", null);
14 +
15 + const settings = {
16 + "title": "Settings page",
17 + "buttons": [
18 + {
19 + "id": "save",
20 + "title": "Save",
21 + "classes": "btn btn-ok"
22 + },
23 + {
24 + "id": "cancel",
25 + "title": "Cancel",
26 + "type": "secondary",
27 + "classes": "btn btn-cancel"
28 + }
29 + ],
30 + "sections": set.settings.sections
31 + }
32 +
33 + modalAD.isOpen = true; // Update directly
34 + modalAD.settings = settings; // Update directly
35 +
36 + return new Promise(resolve => {
37 + this.resolvePromise = resolve;
38 + });
39 + },
40 +
41 + async handleButton(buttonId) {
42 + if (buttonId === 'save') {
43 +
44 + const modalEl = document.getElementById('settingsModal');
45 + const modalAD = Alpine.$data(modalEl);
46 + resp = await window.sendJsonData("/setSettings", modalAD.settings);
47 +
48 + this.resolvePromise({
49 + status: 'saved',
50 + data: resp.settings
51 + });
52 + } else if (buttonId === 'cancel') {
53 + this.handleCancel();
54 + }
55 + this.isOpen = false;
56 + },
57 +
58 + async handleCancel() {
59 + this.resolvePromise({
60 + status: 'cancelled',
61 + data: null
62 + });
63 + this.isOpen = false;
64 + },
65 +
66 + handleFieldButton(field) {
67 + console.log(`Button clicked: ${field.action}`);
68 + }
69 +};
70 +
71 +
72 +// function initSettingsModal() {
73 +
74 +// window.openSettings = function () {
75 +// proxy.openModal().then(result => {
76 +// console.log(result); // This will log the result when the modal is closed
77 +// });
78 +// }
79 +
80 +// return proxy
81 +// }
82 +
83 +
84 +// document.addEventListener('alpine:init', () => {
85 +// Alpine.store('settingsModal', initSettingsModal());
86 +// });
87 +