mbudisic commited on
Commit
4df9c16
Β·
1 Parent(s): 8a8b560

updated configuration to pydantic

Browse files
README.md CHANGED
@@ -61,3 +61,26 @@ chainlit run app.py
61
  - Web search integration via Tavily
62
  - Semantic chunking for better context retrieval
63
  - Interactive chat interface through Chainlit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  - Web search integration via Tavily
62
  - Semantic chunking for better context retrieval
63
  - Interactive chat interface through Chainlit
64
+
65
+ ## βš™οΈ Configuration Options
66
+
67
+ You can customize the behavior of PsTuts RAG using environment variables. Set these in your shell, `.env` file, or deployment environment. Here are the available options:
68
+
69
+ | Env Var | Description |
70
+ |---------|-------------|
71
+ | `EVA_WORKFLOW_NAME` | 🏷️ Name of the EVA workflow. Default: `EVA_workflow` |
72
+ | `EVA_LOG_LEVEL` | πŸͺ΅ Logging level for EVA. Default: `INFO` |
73
+ | `TRANSCRIPT_GLOB` | πŸ“„ Glob pattern for transcript JSON files (supports multiple files separated by `:`). Default: `data/test.json` |
74
+ | `EMBEDDING_MODEL` | 🧊 Name of the embedding model to use (default: custom fine-tuned snowflake model). Default: `mbudisic/snowflake-arctic-embed-s-ft-pstuts` |
75
+ | `EVA_STRIP_THINK` | πŸ’­ If set (present in env), strips 'think' steps from EVA output. |
76
+ | `EMBEDDING_API` | πŸ”Œ API provider for embeddings (`OPENAI`, `HUGGINGFACE`, or `OLLAMA`). Default: `HUGGINGFACE` |
77
+ | `LLM_API` | πŸ€– API provider for LLM (`OPENAI`, `HUGGINGFACE`, or `OLLAMA`). Default: `OLLAMA` |
78
+ | `MAX_RESEARCH_LOOPS` | πŸ” Maximum number of research loops to perform. Default: `3` |
79
+ | `LLM_TOOL_MODEL` | πŸ› οΈ Name of the LLM model to use for tool calling. Default: `smollm2:1.7b-instruct-q2_K` |
80
+ | `N_CONTEXT_DOCS` | πŸ“š Number of context documents to retrieve for RAG. Default: `2` |
81
+ | `EVA_SEARCH_PERMISSION` | 🌐 Permission for search (`yes`, `no`, or `ask`). Default: `no` |
82
+ | `EVA_DB_PERSIST` | πŸ’Ύ Path or flag for DB persistence. Default: unset |
83
+ | `EVA_REINITIALIZE` | πŸ”„ If true, reinitializes EVA DB. Default: `False` |
84
+ | `THREAD_ID` | 🧡 Thread ID for the current session. Default: unset |
85
+
86
+ Set these variables to control model selection, logging, search permissions, and more. For advanced usage, see the developer documentation.
docs/DEVELOPER.md CHANGED
@@ -165,6 +165,31 @@ This feature enables controlled access to external resources while maintaining a
165
  - **`evaluator_utils.py`**: RAG evaluation utilities using RAGAS framework
166
  - **Notebook-based evaluation**: `evaluate_rag.ipynb` for systematic testing
167
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  ## 🎨 UI Customization & Theming
169
 
170
  ### Sepia Theme Implementation πŸ–ΌοΈ
 
165
  - **`evaluator_utils.py`**: RAG evaluation utilities using RAGAS framework
166
  - **Notebook-based evaluation**: `evaluate_rag.ipynb` for systematic testing
167
 
168
+ ### βš™οΈ Configuration Reference
169
+
170
+ The `Configuration` class (in `pstuts_rag/configuration.py`) is powered by Pydantic and supports environment variable overrides for all fields. Below is a reference for all configuration options:
171
+
172
+ | Field | Env Var | Type | Default | Description |
173
+ |-------|---------|------|---------|-------------|
174
+ | `eva_workflow_name` | `EVA_WORKFLOW_NAME` | `str` | `EVA_workflow` | 🏷️ Name of the EVA workflow |
175
+ | `eva_log_level` | `EVA_LOG_LEVEL` | `str` | `INFO` | πŸͺ΅ Logging level for EVA |
176
+ | `transcript_glob` | `TRANSCRIPT_GLOB` | `str` | `data/test.json` | πŸ“„ Glob pattern for transcript JSON files (supports `:` for multiple) |
177
+ | `embedding_model` | `EMBEDDING_MODEL` | `str` | `mbudisic/snowflake-arctic-embed-s-ft-pstuts` | 🧊 Embedding model name (default: custom fine-tuned snowflake) |
178
+ | `eva_strip_think` | `EVA_STRIP_THINK` | `bool` | `False` | πŸ’­ If set (present in env), strips 'think' steps from EVA output |
179
+ | `embedding_api` | `EMBEDDING_API` | `ModelAPI` | `HUGGINGFACE` | πŸ”Œ API provider for embeddings (`OPENAI`, `HUGGINGFACE`, `OLLAMA`) |
180
+ | `llm_api` | `LLM_API` | `ModelAPI` | `OLLAMA` | πŸ€– API provider for LLM (`OPENAI`, `HUGGINGFACE`, `OLLAMA`) |
181
+ | `max_research_loops` | `MAX_RESEARCH_LOOPS` | `int` | `3` | πŸ” Maximum number of research loops to perform |
182
+ | `llm_tool_model` | `LLM_TOOL_MODEL` | `str` | `smollm2:1.7b-instruct-q2_K` | πŸ› οΈ LLM model for tool calling |
183
+ | `n_context_docs` | `N_CONTEXT_DOCS` | `int` | `2` | πŸ“š Number of context documents to retrieve for RAG |
184
+ | `search_permission` | `EVA_SEARCH_PERMISSION` | `str` | `no` | 🌐 Permission for search (`yes`, `no`, `ask`) |
185
+ | `db_persist` | `EVA_DB_PERSIST` | `str or None` | `None` | πŸ’Ύ Path or flag for DB persistence |
186
+ | `eva_reinitialize` | `EVA_REINITIALIZE` | `bool` | `False` | πŸ”„ If true, reinitializes EVA DB |
187
+ | `thread_id` | `THREAD_ID` | `str` | `""` | 🧡 Thread ID for the current session |
188
+
189
+ - All fields can be set via environment variables (see [Pydantic BaseSettings docs](https://docs.pydantic.dev/latest/usage/settings/)).
190
+ - Types are enforced at runtime. Defaults are shown above.
191
+ - For advanced usage, see the `Configuration` class in `pstuts_rag/configuration.py`.
192
+
193
  ## 🎨 UI Customization & Theming
194
 
195
  ### Sepia Theme Implementation πŸ–ΌοΈ
pstuts_rag/pstuts_rag/configuration.py CHANGED
@@ -1,8 +1,9 @@
1
  import os
2
  import logging
3
- from dataclasses import dataclass, fields
4
  from typing import Any, Optional
5
  from enum import Enum
 
 
6
 
7
  from langchain_core.runnables import RunnableConfig
8
 
@@ -21,60 +22,94 @@ class ModelAPI(Enum):
21
  OLLAMA = "OLLAMA"
22
 
23
 
24
- @dataclass(kw_only=True)
25
- class Configuration:
26
  """
27
- Configuration parameters for the application.
28
-
29
- Attributes:
30
- transcript_glob: Glob pattern for transcript JSON files (supports multiple files separated by ':')
31
- embedding_model: Name of the embedding model to use (default: custom fine-tuned snowflake model)
32
- embedding_api: API provider for embeddings (OPENAI or HUGGINGFACE)
33
- max_research_loops: Maximum number of research loops to perform
34
- llm_tool_model: Name of the LLM model to use for tool calling
35
- n_context_docs: Number of context documents to retrieve for RAG
36
  """
37
 
38
- eva_workflow_name: str = str(
39
- os.environ.get("EVA_WORKFLOW_NAME", "EVA_workflow")
 
 
 
40
  )
41
-
42
- eva_log_level: str = str(os.environ.get("EVA_LOG_LEVEL", "INFO")).upper()
43
-
44
- transcript_glob: str = str(
45
- os.environ.get("TRANSCRIPT_GLOB", "data/test.json")
46
  )
47
-
48
- embedding_model: str = str(
49
- os.environ.get(
 
 
 
 
 
50
  "EMBEDDING_MODEL", "mbudisic/snowflake-arctic-embed-s-ft-pstuts"
51
- )
 
52
  )
53
 
54
- eva_strip_think: bool = "EVA_STRIP_THINK" in os.environ
55
-
56
- embedding_api: ModelAPI = ModelAPI(
57
- os.environ.get("EMBEDDING_API", ModelAPI.HUGGINGFACE.value)
 
58
  )
59
-
60
- llm_api: ModelAPI = ModelAPI(
61
- os.environ.get("LLM_API", ModelAPI.OLLAMA.value)
 
 
62
  )
63
-
64
- max_research_loops: int = int(os.environ.get("MAX_RESEARCH_LOOPS", "3"))
65
-
66
- llm_tool_model: str = str(
67
- os.environ.get("LLM_TOOL_MODEL", "smollm2:1.7b-instruct-q2_K")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  )
69
- n_context_docs: int = int(os.environ.get("N_CONTEXT_DOCS", "2"))
70
-
71
- search_permission: str = str(os.environ.get("EVA_SEARCH_PERMISSION", "no"))
72
-
73
- db_persist: str | None = os.environ.get("EVA_DB_PERSIST", None)
74
 
75
- eva_reinitialize: bool = bool(os.environ.get("EVA_REINITIALIZE", "False"))
 
 
 
76
 
77
- thread_id: str = ""
 
 
 
78
 
79
  @classmethod
80
  def from_runnable_config(
@@ -96,16 +131,14 @@ class Configuration:
96
  if config and "configurable" in config
97
  else {}
98
  )
99
- # Map each dataclass field to environment variables or configurable values
100
  # Priority: environment variables > configurable dict values > field defaults
101
  values: dict[str, Any] = {
102
- f.name: os.environ.get(f.name.upper(), configurable.get(f.name))
103
- for f in fields(cls)
104
- if f.init
105
  }
106
  logging.info("Configuration:\n%s", values)
107
-
108
- return cls(**{k: v for k, v in values.items() if v})
109
 
110
  def print(self, print_like_function=logging.info) -> None:
111
  """Print all configuration parameters using the provided logging function.
@@ -117,10 +150,9 @@ class Configuration:
117
  None
118
  """
119
  print_like_function("Configuration parameters:")
120
- for field in fields(self):
121
- if field.init:
122
- value = getattr(self, field.name)
123
- print_like_function(" %s: %s", field.name, value)
124
 
125
  def to_runnable_config(self) -> RunnableConfig:
126
  """Convert Configuration instance to RunnableConfig format.
@@ -129,16 +161,10 @@ class Configuration:
129
  RunnableConfig: Properly formatted configuration for LangGraph
130
  """
131
  configurable_dict = {}
132
-
133
- # Add all non-empty configuration fields to configurable
134
- for field in fields(self):
135
- if field.init:
136
- value = getattr(self, field.name)
137
- if value: # Only include non-empty values
138
- configurable_dict[field.name] = value
139
-
140
- # Ensure thread_id is included if set
141
  if self.thread_id:
142
  configurable_dict["thread_id"] = self.thread_id
143
-
144
  return RunnableConfig(configurable=configurable_dict)
 
1
  import os
2
  import logging
 
3
  from typing import Any, Optional
4
  from enum import Enum
5
+ from pydantic_settings import BaseSettings
6
+ from pydantic import Field
7
 
8
  from langchain_core.runnables import RunnableConfig
9
 
 
22
  OLLAMA = "OLLAMA"
23
 
24
 
25
+ class Configuration(BaseSettings):
 
26
  """
27
+ Configuration parameters for the application. All fields can be set via environment variables.
 
 
 
 
 
 
 
 
28
  """
29
 
30
+ eva_workflow_name: str = Field(
31
+ default_factory=lambda: os.environ.get(
32
+ "EVA_WORKFLOW_NAME", "EVA_workflow"
33
+ ),
34
+ description="Name of the EVA workflow. Set via EVA_WORKFLOW_NAME.",
35
  )
36
+ eva_log_level: str = Field(
37
+ default_factory=lambda: os.environ.get(
38
+ "EVA_LOG_LEVEL", "INFO"
39
+ ).upper(),
40
+ description="Logging level for EVA. Set via EVA_LOG_LEVEL.",
41
  )
42
+ transcript_glob: str = Field(
43
+ default_factory=lambda: os.environ.get(
44
+ "TRANSCRIPT_GLOB", "data/test.json"
45
+ ),
46
+ description="Glob pattern for transcript JSON files (supports multiple files separated by ':'). Set via TRANSCRIPT_GLOB.",
47
+ )
48
+ embedding_model: str = Field(
49
+ default_factory=lambda: os.environ.get(
50
  "EMBEDDING_MODEL", "mbudisic/snowflake-arctic-embed-s-ft-pstuts"
51
+ ),
52
+ description="Name of the embedding model to use (default: custom fine-tuned snowflake model). Set via EMBEDDING_MODEL.",
53
  )
54
 
55
+ embedding_api: ModelAPI = Field(
56
+ default_factory=lambda: ModelAPI(
57
+ os.environ.get("EMBEDDING_API", ModelAPI.HUGGINGFACE.value)
58
+ ),
59
+ description="API provider for embeddings (OPENAI, HUGGINGFACE, or OLLAMA). Set via EMBEDDING_API.",
60
  )
61
+ llm_api: ModelAPI = Field(
62
+ default_factory=lambda: ModelAPI(
63
+ os.environ.get("LLM_API", ModelAPI.OLLAMA.value)
64
+ ),
65
+ description="API provider for LLM (OPENAI, HUGGINGFACE, or OLLAMA). Set via LLM_API.",
66
  )
67
+ max_research_loops: int = Field(
68
+ default_factory=lambda: int(os.environ.get("MAX_RESEARCH_LOOPS", "3")),
69
+ description="Maximum number of research loops to perform. Set via MAX_RESEARCH_LOOPS.",
70
+ )
71
+ llm_tool_model: str = Field(
72
+ default_factory=lambda: os.environ.get(
73
+ "LLM_TOOL_MODEL", "smollm2:1.7b-instruct-q2_K"
74
+ ),
75
+ description="Name of the LLM model to use for tool calling. Set via LLM_TOOL_MODEL.",
76
+ )
77
+ n_context_docs: int = Field(
78
+ default_factory=lambda: int(os.environ.get("N_CONTEXT_DOCS", "2")),
79
+ description="Number of context documents to retrieve for RAG. Set via N_CONTEXT_DOCS.",
80
+ )
81
+ search_permission: str = Field(
82
+ default_factory=lambda: os.environ.get("EVA_SEARCH_PERMISSION", "no"),
83
+ description="Permission for search (yes/no). Set via EVA_SEARCH_PERMISSION.",
84
+ )
85
+ db_persist: Optional[str] = Field(
86
+ default_factory=lambda: os.environ.get("EVA_DB_PERSIST", None),
87
+ description="Path or flag for DB persistence. Set via EVA_DB_PERSIST.",
88
+ )
89
+ eva_reinitialize: bool = Field(
90
+ default_factory=lambda: os.environ.get(
91
+ "EVA_REINITIALIZE", "False"
92
+ ).lower()
93
+ in ("true", "1", "yes"),
94
+ description="If true, reinitializes EVA DB. Set via EVA_REINITIALIZE.",
95
+ )
96
+ eva_strip_think: bool = Field(
97
+ default_factory=lambda: os.environ.get(
98
+ "EVA_STRIP_THINK", "True"
99
+ ).lower()
100
+ in ("true", "1", "yes"),
101
+ description="If true (default) strips thinking tags from LLM responses. Set via EVA_STRIP_THINK.",
102
  )
 
 
 
 
 
103
 
104
+ thread_id: str = Field(
105
+ default="",
106
+ description="Thread ID for the current session. Set via THREAD_ID.",
107
+ )
108
 
109
+ class Config:
110
+ env_file = ".env"
111
+ env_file_encoding = "utf-8"
112
+ extra = "ignore" # Allow extra env vars in .env/environment
113
 
114
  @classmethod
115
  def from_runnable_config(
 
131
  if config and "configurable" in config
132
  else {}
133
  )
134
+ # Map each field to environment variables or configurable values
135
  # Priority: environment variables > configurable dict values > field defaults
136
  values: dict[str, Any] = {
137
+ name: os.environ.get(name.upper(), configurable.get(name))
138
+ for name in cls.__fields__
 
139
  }
140
  logging.info("Configuration:\n%s", values)
141
+ return cls(**{k: v for k, v in values.items() if v is not None})
 
142
 
143
  def print(self, print_like_function=logging.info) -> None:
144
  """Print all configuration parameters using the provided logging function.
 
150
  None
151
  """
152
  print_like_function("Configuration parameters:")
153
+ for name, field in self.__fields__.items():
154
+ value = getattr(self, name)
155
+ print_like_function(" %s: %s", name, value)
 
156
 
157
  def to_runnable_config(self) -> RunnableConfig:
158
  """Convert Configuration instance to RunnableConfig format.
 
161
  RunnableConfig: Properly formatted configuration for LangGraph
162
  """
163
  configurable_dict = {}
164
+ for name in self.__fields__:
165
+ value = getattr(self, name)
166
+ if value:
167
+ configurable_dict[name] = value
 
 
 
 
 
168
  if self.thread_id:
169
  configurable_dict["thread_id"] = self.thread_id
 
170
  return RunnableConfig(configurable=configurable_dict)
pyproject.toml CHANGED
@@ -52,6 +52,7 @@ dependencies = [
52
  "langchain-tavily>=0.2.0",
53
  "beautifulsoup4>=4.13.4",
54
  "pathvalidate>=3.2.3",
 
55
  ]
56
  authors = [{ name = "Marko Budisic", email = "[email protected]" }]
57
  license = "MIT"
 
52
  "langchain-tavily>=0.2.0",
53
  "beautifulsoup4>=4.13.4",
54
  "pathvalidate>=3.2.3",
55
+ "pydantic-settings>=2.9.1",
56
  ]
57
  authors = [{ name = "Marko Budisic", email = "[email protected]" }]
58
  license = "MIT"
uv.lock CHANGED
@@ -3777,6 +3777,7 @@ dependencies = [
3777
  { name = "pandas" },
3778
  { name = "pathvalidate" },
3779
  { name = "pyarrow" },
 
3780
  { name = "python-dotenv" },
3781
  { name = "qdrant-client" },
3782
  { name = "ragas" },
@@ -3850,6 +3851,7 @@ requires-dist = [
3850
  { name = "pandas", specifier = ">=2.0.0" },
3851
  { name = "pathvalidate", specifier = ">=3.2.3" },
3852
  { name = "pyarrow", specifier = ">=19.0.0" },
 
3853
  { name = "pylint-venv", marker = "extra == 'dev'", specifier = ">=3.0.4" },
3854
  { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" },
3855
  { name = "python-dotenv", specifier = ">=0.9.9" },
 
3777
  { name = "pandas" },
3778
  { name = "pathvalidate" },
3779
  { name = "pyarrow" },
3780
+ { name = "pydantic-settings" },
3781
  { name = "python-dotenv" },
3782
  { name = "qdrant-client" },
3783
  { name = "ragas" },
 
3851
  { name = "pandas", specifier = ">=2.0.0" },
3852
  { name = "pathvalidate", specifier = ">=3.2.3" },
3853
  { name = "pyarrow", specifier = ">=19.0.0" },
3854
+ { name = "pydantic-settings", specifier = ">=2.9.1" },
3855
  { name = "pylint-venv", marker = "extra == 'dev'", specifier = ">=3.0.4" },
3856
  { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" },
3857
  { name = "python-dotenv", specifier = ">=0.9.9" },