remdb 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of remdb might be problematic. Click here for more details.
- rem/__init__.py +2 -0
- rem/agentic/README.md +650 -0
- rem/agentic/__init__.py +39 -0
- rem/agentic/agents/README.md +155 -0
- rem/agentic/agents/__init__.py +8 -0
- rem/agentic/context.py +148 -0
- rem/agentic/context_builder.py +329 -0
- rem/agentic/mcp/__init__.py +0 -0
- rem/agentic/mcp/tool_wrapper.py +107 -0
- rem/agentic/otel/__init__.py +5 -0
- rem/agentic/otel/setup.py +151 -0
- rem/agentic/providers/phoenix.py +674 -0
- rem/agentic/providers/pydantic_ai.py +572 -0
- rem/agentic/query.py +117 -0
- rem/agentic/query_helper.py +89 -0
- rem/agentic/schema.py +396 -0
- rem/agentic/serialization.py +245 -0
- rem/agentic/tools/__init__.py +5 -0
- rem/agentic/tools/rem_tools.py +231 -0
- rem/api/README.md +420 -0
- rem/api/main.py +324 -0
- rem/api/mcp_router/prompts.py +182 -0
- rem/api/mcp_router/resources.py +536 -0
- rem/api/mcp_router/server.py +213 -0
- rem/api/mcp_router/tools.py +584 -0
- rem/api/routers/auth.py +229 -0
- rem/api/routers/chat/__init__.py +5 -0
- rem/api/routers/chat/completions.py +281 -0
- rem/api/routers/chat/json_utils.py +76 -0
- rem/api/routers/chat/models.py +124 -0
- rem/api/routers/chat/streaming.py +185 -0
- rem/auth/README.md +258 -0
- rem/auth/__init__.py +26 -0
- rem/auth/middleware.py +100 -0
- rem/auth/providers/__init__.py +13 -0
- rem/auth/providers/base.py +376 -0
- rem/auth/providers/google.py +163 -0
- rem/auth/providers/microsoft.py +237 -0
- rem/cli/README.md +455 -0
- rem/cli/__init__.py +8 -0
- rem/cli/commands/README.md +126 -0
- rem/cli/commands/__init__.py +3 -0
- rem/cli/commands/ask.py +566 -0
- rem/cli/commands/configure.py +497 -0
- rem/cli/commands/db.py +493 -0
- rem/cli/commands/dreaming.py +324 -0
- rem/cli/commands/experiments.py +1302 -0
- rem/cli/commands/mcp.py +66 -0
- rem/cli/commands/process.py +245 -0
- rem/cli/commands/schema.py +183 -0
- rem/cli/commands/serve.py +106 -0
- rem/cli/dreaming.py +363 -0
- rem/cli/main.py +96 -0
- rem/config.py +237 -0
- rem/mcp_server.py +41 -0
- rem/models/core/__init__.py +49 -0
- rem/models/core/core_model.py +64 -0
- rem/models/core/engram.py +333 -0
- rem/models/core/experiment.py +628 -0
- rem/models/core/inline_edge.py +132 -0
- rem/models/core/rem_query.py +243 -0
- rem/models/entities/__init__.py +43 -0
- rem/models/entities/file.py +57 -0
- rem/models/entities/image_resource.py +88 -0
- rem/models/entities/message.py +35 -0
- rem/models/entities/moment.py +123 -0
- rem/models/entities/ontology.py +191 -0
- rem/models/entities/ontology_config.py +131 -0
- rem/models/entities/resource.py +95 -0
- rem/models/entities/schema.py +87 -0
- rem/models/entities/user.py +85 -0
- rem/py.typed +0 -0
- rem/schemas/README.md +507 -0
- rem/schemas/__init__.py +6 -0
- rem/schemas/agents/README.md +92 -0
- rem/schemas/agents/core/moment-builder.yaml +178 -0
- rem/schemas/agents/core/rem-query-agent.yaml +226 -0
- rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
- rem/schemas/agents/core/simple-assistant.yaml +19 -0
- rem/schemas/agents/core/user-profile-builder.yaml +163 -0
- rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
- rem/schemas/agents/examples/contract-extractor.yaml +134 -0
- rem/schemas/agents/examples/cv-parser.yaml +263 -0
- rem/schemas/agents/examples/hello-world.yaml +37 -0
- rem/schemas/agents/examples/query.yaml +54 -0
- rem/schemas/agents/examples/simple.yaml +21 -0
- rem/schemas/agents/examples/test.yaml +29 -0
- rem/schemas/agents/rem.yaml +128 -0
- rem/schemas/evaluators/hello-world/default.yaml +77 -0
- rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
- rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
- rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
- rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
- rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
- rem/services/__init__.py +16 -0
- rem/services/audio/INTEGRATION.md +308 -0
- rem/services/audio/README.md +376 -0
- rem/services/audio/__init__.py +15 -0
- rem/services/audio/chunker.py +354 -0
- rem/services/audio/transcriber.py +259 -0
- rem/services/content/README.md +1269 -0
- rem/services/content/__init__.py +5 -0
- rem/services/content/providers.py +806 -0
- rem/services/content/service.py +676 -0
- rem/services/dreaming/README.md +230 -0
- rem/services/dreaming/__init__.py +53 -0
- rem/services/dreaming/affinity_service.py +336 -0
- rem/services/dreaming/moment_service.py +264 -0
- rem/services/dreaming/ontology_service.py +54 -0
- rem/services/dreaming/user_model_service.py +297 -0
- rem/services/dreaming/utils.py +39 -0
- rem/services/embeddings/__init__.py +11 -0
- rem/services/embeddings/api.py +120 -0
- rem/services/embeddings/worker.py +421 -0
- rem/services/fs/README.md +662 -0
- rem/services/fs/__init__.py +62 -0
- rem/services/fs/examples.py +206 -0
- rem/services/fs/examples_paths.py +204 -0
- rem/services/fs/git_provider.py +935 -0
- rem/services/fs/local_provider.py +760 -0
- rem/services/fs/parsing-hooks-examples.md +172 -0
- rem/services/fs/paths.py +276 -0
- rem/services/fs/provider.py +460 -0
- rem/services/fs/s3_provider.py +1042 -0
- rem/services/fs/service.py +186 -0
- rem/services/git/README.md +1075 -0
- rem/services/git/__init__.py +17 -0
- rem/services/git/service.py +469 -0
- rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
- rem/services/phoenix/README.md +453 -0
- rem/services/phoenix/__init__.py +46 -0
- rem/services/phoenix/client.py +686 -0
- rem/services/phoenix/config.py +88 -0
- rem/services/phoenix/prompt_labels.py +477 -0
- rem/services/postgres/README.md +575 -0
- rem/services/postgres/__init__.py +23 -0
- rem/services/postgres/migration_service.py +427 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +232 -0
- rem/services/postgres/register_type.py +352 -0
- rem/services/postgres/repository.py +337 -0
- rem/services/postgres/schema_generator.py +379 -0
- rem/services/postgres/service.py +802 -0
- rem/services/postgres/sql_builder.py +354 -0
- rem/services/rem/README.md +304 -0
- rem/services/rem/__init__.py +23 -0
- rem/services/rem/exceptions.py +71 -0
- rem/services/rem/executor.py +293 -0
- rem/services/rem/parser.py +145 -0
- rem/services/rem/queries.py +196 -0
- rem/services/rem/query.py +371 -0
- rem/services/rem/service.py +527 -0
- rem/services/session/README.md +374 -0
- rem/services/session/__init__.py +6 -0
- rem/services/session/compression.py +360 -0
- rem/services/session/reload.py +77 -0
- rem/settings.py +1235 -0
- rem/sql/002_install_models.sql +1068 -0
- rem/sql/background_indexes.sql +42 -0
- rem/sql/install_models.sql +1038 -0
- rem/sql/migrations/001_install.sql +503 -0
- rem/sql/migrations/002_install_models.sql +1202 -0
- rem/utils/AGENTIC_CHUNKING.md +597 -0
- rem/utils/README.md +583 -0
- rem/utils/__init__.py +43 -0
- rem/utils/agentic_chunking.py +622 -0
- rem/utils/batch_ops.py +343 -0
- rem/utils/chunking.py +108 -0
- rem/utils/clip_embeddings.py +276 -0
- rem/utils/dict_utils.py +98 -0
- rem/utils/embeddings.py +423 -0
- rem/utils/examples/embeddings_example.py +305 -0
- rem/utils/examples/sql_types_example.py +202 -0
- rem/utils/markdown.py +16 -0
- rem/utils/model_helpers.py +236 -0
- rem/utils/schema_loader.py +336 -0
- rem/utils/sql_types.py +348 -0
- rem/utils/user_id.py +81 -0
- rem/utils/vision.py +330 -0
- rem/workers/README.md +506 -0
- rem/workers/__init__.py +5 -0
- rem/workers/dreaming.py +502 -0
- rem/workers/engram_processor.py +312 -0
- rem/workers/sqs_file_processor.py +193 -0
- remdb-0.3.0.dist-info/METADATA +1455 -0
- remdb-0.3.0.dist-info/RECORD +187 -0
- remdb-0.3.0.dist-info/WHEEL +4 -0
- remdb-0.3.0.dist-info/entry_points.txt +2 -0
rem/settings.py
ADDED
|
@@ -0,0 +1,1235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
REM Settings and Configuration.
|
|
3
|
+
|
|
4
|
+
Pydantic settings with environment variable support:
|
|
5
|
+
- Nested settings with env_prefix for organization
|
|
6
|
+
- Environment variables use double underscore delimiter (ENV__NESTED__VAR)
|
|
7
|
+
- Sensitive defaults (auth disabled, OTEL disabled for local dev)
|
|
8
|
+
- Global settings singleton
|
|
9
|
+
|
|
10
|
+
Example .env file:
|
|
11
|
+
# API Server
|
|
12
|
+
API__HOST=0.0.0.0
|
|
13
|
+
API__PORT=8000
|
|
14
|
+
API__RELOAD=true
|
|
15
|
+
API__LOG_LEVEL=info
|
|
16
|
+
|
|
17
|
+
# LLM
|
|
18
|
+
LLM__DEFAULT_MODEL=anthropic:claude-sonnet-4-5-20250929
|
|
19
|
+
LLM__DEFAULT_TEMPERATURE=0.5
|
|
20
|
+
LLM__MAX_RETRIES=10
|
|
21
|
+
LLM__OPENAI_API_KEY=sk-...
|
|
22
|
+
LLM__ANTHROPIC_API_KEY=sk-ant-...
|
|
23
|
+
|
|
24
|
+
# Database (port 5050 for Docker Compose)
|
|
25
|
+
POSTGRES__CONNECTION_STRING=postgresql://rem:rem@localhost:5050/rem
|
|
26
|
+
POSTGRES__POOL_MIN_SIZE=5
|
|
27
|
+
POSTGRES__POOL_MAX_SIZE=20
|
|
28
|
+
POSTGRES__STATEMENT_TIMEOUT=30000
|
|
29
|
+
|
|
30
|
+
# Auth (disabled by default)
|
|
31
|
+
AUTH__ENABLED=false
|
|
32
|
+
AUTH__OIDC_ISSUER_URL=https://accounts.google.com
|
|
33
|
+
AUTH__OIDC_CLIENT_ID=your-client-id
|
|
34
|
+
AUTH__SESSION_SECRET=your-secret-key
|
|
35
|
+
|
|
36
|
+
# OpenTelemetry (disabled by default)
|
|
37
|
+
OTEL__ENABLED=false
|
|
38
|
+
OTEL__SERVICE_NAME=rem-api
|
|
39
|
+
OTEL__COLLECTOR_ENDPOINT=http://localhost:4318
|
|
40
|
+
OTEL__PROTOCOL=http
|
|
41
|
+
|
|
42
|
+
# Arize Phoenix (disabled by default)
|
|
43
|
+
PHOENIX__ENABLED=false
|
|
44
|
+
PHOENIX__COLLECTOR_ENDPOINT=http://localhost:6006/v1/traces
|
|
45
|
+
PHOENIX__PROJECT_NAME=rem
|
|
46
|
+
|
|
47
|
+
# S3 Storage
|
|
48
|
+
S3__BUCKET_NAME=rem-storage
|
|
49
|
+
S3__REGION=us-east-1
|
|
50
|
+
S3__ENDPOINT_URL=http://localhost:9000 # For MinIO
|
|
51
|
+
S3__ACCESS_KEY_ID=minioadmin
|
|
52
|
+
S3__SECRET_ACCESS_KEY=minioadmin
|
|
53
|
+
|
|
54
|
+
# Environment
|
|
55
|
+
ENVIRONMENT=development
|
|
56
|
+
TEAM=rem
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
import os
|
|
60
|
+
from pydantic import Field, field_validator
|
|
61
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class LLMSettings(BaseSettings):
|
|
65
|
+
"""
|
|
66
|
+
LLM provider settings for Pydantic AI agents.
|
|
67
|
+
|
|
68
|
+
Environment variables (accepts both prefixed and unprefixed):
|
|
69
|
+
LLM__DEFAULT_MODEL or DEFAULT_MODEL - Default model (format: provider:model-id)
|
|
70
|
+
LLM__DEFAULT_TEMPERATURE or DEFAULT_TEMPERATURE - Temperature for generation
|
|
71
|
+
LLM__MAX_RETRIES or MAX_RETRIES - Max agent request retries
|
|
72
|
+
LLM__EVALUATOR_MODEL or EVALUATOR_MODEL - Model for LLM-as-judge evaluation
|
|
73
|
+
LLM__OPENAI_API_KEY or OPENAI_API_KEY - OpenAI API key
|
|
74
|
+
LLM__ANTHROPIC_API_KEY or ANTHROPIC_API_KEY - Anthropic API key
|
|
75
|
+
LLM__EMBEDDING_PROVIDER or EMBEDDING_PROVIDER - Default embedding provider (openai, cohere, jina, etc.)
|
|
76
|
+
LLM__EMBEDDING_MODEL or EMBEDDING_MODEL - Default embedding model name
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
model_config = SettingsConfigDict(
|
|
80
|
+
env_prefix="LLM__",
|
|
81
|
+
env_file=".env",
|
|
82
|
+
env_file_encoding="utf-8",
|
|
83
|
+
extra="ignore",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
default_model: str = Field(
|
|
87
|
+
default="anthropic:claude-sonnet-4-5-20250929",
|
|
88
|
+
description="Default LLM model (format: provider:model-id)",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
default_temperature: float = Field(
|
|
92
|
+
default=0.5,
|
|
93
|
+
ge=0.0,
|
|
94
|
+
le=1.0,
|
|
95
|
+
description="Default temperature (0.0-0.3: analytical, 0.7-1.0: creative)",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
max_retries: int = Field(
|
|
99
|
+
default=10,
|
|
100
|
+
description="Maximum agent request retries (prevents infinite loops from tool errors)",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
default_max_iterations: int = Field(
|
|
104
|
+
default=20,
|
|
105
|
+
description="Default max iterations for agentic calls (limits total LLM requests per agent.run())",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
evaluator_model: str = Field(
|
|
109
|
+
default="gpt-4.1",
|
|
110
|
+
description="Model for LLM-as-judge evaluators (separate from generation model)",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
query_agent_model: str = Field(
|
|
114
|
+
default="cerebras:qwen-3-32b",
|
|
115
|
+
description="Model for REM Query Agent (natural language to REM query). Cerebras Qwen 3-32B provides ultra-fast inference (1.2s reasoning, 2400 tok/s). Alternative: cerebras:llama-3.3-70b, gpt-4o-mini, or claude-sonnet-4.5",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
openai_api_key: str | None = Field(
|
|
119
|
+
default=None,
|
|
120
|
+
description="OpenAI API key for GPT models (reads from LLM__OPENAI_API_KEY or OPENAI_API_KEY)",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
anthropic_api_key: str | None = Field(
|
|
124
|
+
default=None,
|
|
125
|
+
description="Anthropic API key for Claude models (reads from LLM__ANTHROPIC_API_KEY or ANTHROPIC_API_KEY)",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
embedding_provider: str = Field(
|
|
129
|
+
default="openai",
|
|
130
|
+
description="Default embedding provider (openai, cohere, jina, etc.)",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
embedding_model: str = Field(
|
|
134
|
+
default="text-embedding-3-small",
|
|
135
|
+
description="Default embedding model (provider-specific model name)",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
@field_validator("openai_api_key", mode="before")
|
|
139
|
+
@classmethod
|
|
140
|
+
def validate_openai_api_key(cls, v):
|
|
141
|
+
"""Fallback to OPENAI_API_KEY if LLM__OPENAI_API_KEY not set (LLM__ takes precedence)."""
|
|
142
|
+
if v is None:
|
|
143
|
+
return os.getenv("OPENAI_API_KEY")
|
|
144
|
+
return v
|
|
145
|
+
|
|
146
|
+
@field_validator("anthropic_api_key", mode="before")
|
|
147
|
+
@classmethod
|
|
148
|
+
def validate_anthropic_api_key(cls, v):
|
|
149
|
+
"""Fallback to ANTHROPIC_API_KEY if LLM__ANTHROPIC_API_KEY not set (LLM__ takes precedence)."""
|
|
150
|
+
if v is None:
|
|
151
|
+
return os.getenv("ANTHROPIC_API_KEY")
|
|
152
|
+
return v
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class MCPSettings(BaseSettings):
|
|
156
|
+
"""
|
|
157
|
+
MCP server settings.
|
|
158
|
+
|
|
159
|
+
MCP server is mounted at /api/v1/mcp with FastMCP.
|
|
160
|
+
Can be accessed via:
|
|
161
|
+
- HTTP transport (production): /api/v1/mcp
|
|
162
|
+
- SSE transport (compatible with Claude Desktop)
|
|
163
|
+
|
|
164
|
+
Environment variables:
|
|
165
|
+
MCP_SERVER_{NAME} - Server URLs for MCP client connections
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
model_config = SettingsConfigDict(
|
|
169
|
+
env_prefix="MCP__",
|
|
170
|
+
env_file=".env",
|
|
171
|
+
env_file_encoding="utf-8",
|
|
172
|
+
extra="ignore",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def get_server_url(server_name: str) -> str | None:
|
|
177
|
+
"""
|
|
178
|
+
Get MCP server URL from environment variable.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
server_name: Server name (e.g., "test", "prod")
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Server URL or None if not configured
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
MCP_SERVER_TEST=http://localhost:8000/api/v1/mcp
|
|
188
|
+
"""
|
|
189
|
+
import os
|
|
190
|
+
|
|
191
|
+
env_key = f"MCP_SERVER_{server_name.upper()}"
|
|
192
|
+
return os.getenv(env_key)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class OTELSettings(BaseSettings):
|
|
196
|
+
"""
|
|
197
|
+
OpenTelemetry observability settings.
|
|
198
|
+
|
|
199
|
+
Integrates with OpenTelemetry Collector for distributed tracing.
|
|
200
|
+
Uses OTLP protocol to export to Arize Phoenix or other OTLP backends.
|
|
201
|
+
|
|
202
|
+
Environment variables:
|
|
203
|
+
OTEL__ENABLED - Enable instrumentation (default: false for local dev)
|
|
204
|
+
OTEL__SERVICE_NAME - Service name for traces
|
|
205
|
+
OTEL__COLLECTOR_ENDPOINT - OTLP endpoint (gRPC: 4317, HTTP: 4318)
|
|
206
|
+
OTEL__PROTOCOL - Protocol to use (grpc or http)
|
|
207
|
+
OTEL__EXPORT_TIMEOUT - Export timeout in milliseconds
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
model_config = SettingsConfigDict(
|
|
211
|
+
env_prefix="OTEL__",
|
|
212
|
+
env_file=".env",
|
|
213
|
+
env_file_encoding="utf-8",
|
|
214
|
+
extra="ignore",
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
enabled: bool = Field(
|
|
218
|
+
default=False,
|
|
219
|
+
description="Enable OpenTelemetry instrumentation (disabled by default for local dev)",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
service_name: str = Field(
|
|
223
|
+
default="rem-api",
|
|
224
|
+
description="Service name for traces",
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
collector_endpoint: str = Field(
|
|
228
|
+
default="http://localhost:4318",
|
|
229
|
+
description="OTLP collector endpoint (HTTP: 4318, gRPC: 4317)",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
protocol: str = Field(
|
|
233
|
+
default="http",
|
|
234
|
+
description="OTLP protocol (http or grpc)",
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
export_timeout: int = Field(
|
|
238
|
+
default=10000,
|
|
239
|
+
description="Export timeout in milliseconds",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class PhoenixSettings(BaseSettings):
|
|
244
|
+
"""
|
|
245
|
+
Arize Phoenix settings for LLM observability and evaluation.
|
|
246
|
+
|
|
247
|
+
Phoenix provides:
|
|
248
|
+
- OpenTelemetry-based LLM tracing (OpenInference conventions)
|
|
249
|
+
- Experiment tracking
|
|
250
|
+
- Evaluation feedback
|
|
251
|
+
|
|
252
|
+
Environment variables:
|
|
253
|
+
PHOENIX__ENABLED - Enable Phoenix integration
|
|
254
|
+
PHOENIX__BASE_URL - Phoenix base URL (for client connections)
|
|
255
|
+
PHOENIX__API_KEY - Phoenix API key (cloud instances)
|
|
256
|
+
PHOENIX__COLLECTOR_ENDPOINT - Phoenix OTLP endpoint
|
|
257
|
+
PHOENIX__PROJECT_NAME - Phoenix project name for trace organization
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
model_config = SettingsConfigDict(
|
|
261
|
+
env_prefix="PHOENIX__",
|
|
262
|
+
env_file=".env",
|
|
263
|
+
env_file_encoding="utf-8",
|
|
264
|
+
extra="ignore",
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
enabled: bool = Field(
|
|
268
|
+
default=False,
|
|
269
|
+
description="Enable Phoenix integration (disabled by default for local dev)",
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
base_url: str = Field(
|
|
273
|
+
default="http://localhost:6006",
|
|
274
|
+
description="Phoenix base URL for client connections (default local Phoenix port)",
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
api_key: str | None = Field(
|
|
278
|
+
default=None,
|
|
279
|
+
description="Arize Phoenix API key for cloud instances",
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
collector_endpoint: str = Field(
|
|
283
|
+
default="http://localhost:6006/v1/traces",
|
|
284
|
+
description="Phoenix OTLP endpoint for traces (default local Phoenix port)",
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
project_name: str = Field(
|
|
288
|
+
default="rem",
|
|
289
|
+
description="Phoenix project name for trace organization",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class GoogleOAuthSettings(BaseSettings):
|
|
294
|
+
"""
|
|
295
|
+
Google OAuth settings.
|
|
296
|
+
|
|
297
|
+
Environment variables:
|
|
298
|
+
AUTH__GOOGLE__CLIENT_ID - Google OAuth client ID
|
|
299
|
+
AUTH__GOOGLE__CLIENT_SECRET - Google OAuth client secret
|
|
300
|
+
AUTH__GOOGLE__REDIRECT_URI - OAuth callback URL
|
|
301
|
+
AUTH__GOOGLE__HOSTED_DOMAIN - Restrict to Google Workspace domain
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
model_config = SettingsConfigDict(
|
|
305
|
+
env_prefix="AUTH__GOOGLE__",
|
|
306
|
+
env_file=".env",
|
|
307
|
+
env_file_encoding="utf-8",
|
|
308
|
+
extra="ignore",
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
client_id: str = Field(default="", description="Google OAuth client ID")
|
|
312
|
+
client_secret: str = Field(default="", description="Google OAuth client secret")
|
|
313
|
+
redirect_uri: str = Field(
|
|
314
|
+
default="http://localhost:8000/api/auth/google/callback",
|
|
315
|
+
description="OAuth redirect URI",
|
|
316
|
+
)
|
|
317
|
+
hosted_domain: str | None = Field(
|
|
318
|
+
default=None, description="Restrict to Google Workspace domain (e.g., example.com)"
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class MicrosoftOAuthSettings(BaseSettings):
|
|
323
|
+
"""
|
|
324
|
+
Microsoft Entra ID OAuth settings.
|
|
325
|
+
|
|
326
|
+
Environment variables:
|
|
327
|
+
AUTH__MICROSOFT__CLIENT_ID - Application (client) ID
|
|
328
|
+
AUTH__MICROSOFT__CLIENT_SECRET - Client secret
|
|
329
|
+
AUTH__MICROSOFT__REDIRECT_URI - OAuth callback URL
|
|
330
|
+
AUTH__MICROSOFT__TENANT - Tenant ID or common/organizations/consumers
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
model_config = SettingsConfigDict(
|
|
334
|
+
env_prefix="AUTH__MICROSOFT__",
|
|
335
|
+
env_file=".env",
|
|
336
|
+
env_file_encoding="utf-8",
|
|
337
|
+
extra="ignore",
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
client_id: str = Field(default="", description="Microsoft Application ID")
|
|
341
|
+
client_secret: str = Field(default="", description="Microsoft client secret")
|
|
342
|
+
redirect_uri: str = Field(
|
|
343
|
+
default="http://localhost:8000/api/auth/microsoft/callback",
|
|
344
|
+
description="OAuth redirect URI",
|
|
345
|
+
)
|
|
346
|
+
tenant: str = Field(
|
|
347
|
+
default="common",
|
|
348
|
+
description="Tenant ID or common/organizations/consumers",
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class AuthSettings(BaseSettings):
|
|
353
|
+
"""
|
|
354
|
+
Authentication settings for OAuth 2.1 / OIDC.
|
|
355
|
+
|
|
356
|
+
Supports multiple providers:
|
|
357
|
+
- Google OAuth
|
|
358
|
+
- Microsoft Entra ID
|
|
359
|
+
- Custom OIDC provider
|
|
360
|
+
|
|
361
|
+
Environment variables:
|
|
362
|
+
AUTH__ENABLED - Enable authentication (default: false)
|
|
363
|
+
AUTH__SESSION_SECRET - Secret for session cookie signing
|
|
364
|
+
AUTH__GOOGLE__* - Google OAuth settings
|
|
365
|
+
AUTH__MICROSOFT__* - Microsoft OAuth settings
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
model_config = SettingsConfigDict(
|
|
369
|
+
env_prefix="AUTH__",
|
|
370
|
+
env_file=".env",
|
|
371
|
+
env_file_encoding="utf-8",
|
|
372
|
+
extra="ignore",
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
enabled: bool = Field(
|
|
376
|
+
default=False,
|
|
377
|
+
description="Enable authentication (disabled by default for testing)",
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
session_secret: str = Field(
|
|
381
|
+
default="",
|
|
382
|
+
description="Secret key for session cookie signing (generate with secrets.token_hex(32))",
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# OAuth provider settings
|
|
386
|
+
google: GoogleOAuthSettings = Field(default_factory=GoogleOAuthSettings)
|
|
387
|
+
microsoft: MicrosoftOAuthSettings = Field(default_factory=MicrosoftOAuthSettings)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
class PostgresSettings(BaseSettings):
|
|
391
|
+
"""
|
|
392
|
+
PostgreSQL settings for CloudNativePG.
|
|
393
|
+
|
|
394
|
+
Connects to PostgreSQL 18 with pgvector extension running on CloudNativePG.
|
|
395
|
+
|
|
396
|
+
Environment variables:
|
|
397
|
+
POSTGRES__ENABLED - Enable database connection (default: true)
|
|
398
|
+
POSTGRES__CONNECTION_STRING - PostgreSQL connection string
|
|
399
|
+
POSTGRES__POOL_SIZE - Connection pool size
|
|
400
|
+
POSTGRES__POOL_MIN_SIZE - Minimum pool size
|
|
401
|
+
POSTGRES__POOL_MAX_SIZE - Maximum pool size
|
|
402
|
+
POSTGRES__POOL_TIMEOUT - Connection timeout in seconds
|
|
403
|
+
POSTGRES__STATEMENT_TIMEOUT - Statement timeout in milliseconds
|
|
404
|
+
"""
|
|
405
|
+
|
|
406
|
+
model_config = SettingsConfigDict(
|
|
407
|
+
env_prefix="POSTGRES__",
|
|
408
|
+
env_file=".env",
|
|
409
|
+
env_file_encoding="utf-8",
|
|
410
|
+
extra="ignore",
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
enabled: bool = Field(
|
|
414
|
+
default=True,
|
|
415
|
+
description="Enable database connection (set to false for testing without DB)",
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
connection_string: str = Field(
|
|
419
|
+
default="postgresql://rem:rem@localhost:5050/rem",
|
|
420
|
+
description="PostgreSQL connection string (default uses Docker Compose port 5050)",
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
pool_size: int = Field(
|
|
424
|
+
default=10,
|
|
425
|
+
description="Connection pool size (deprecated, use pool_min_size/pool_max_size)",
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
pool_min_size: int = Field(
|
|
429
|
+
default=5,
|
|
430
|
+
description="Minimum number of connections in pool",
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
pool_max_size: int = Field(
|
|
434
|
+
default=20,
|
|
435
|
+
description="Maximum number of connections in pool",
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
pool_timeout: int = Field(
|
|
439
|
+
default=30,
|
|
440
|
+
description="Connection timeout in seconds",
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
statement_timeout: int = Field(
|
|
444
|
+
default=30000,
|
|
445
|
+
description="Statement timeout in milliseconds (30 seconds default)",
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def user(self) -> str:
|
|
450
|
+
from urllib.parse import urlparse
|
|
451
|
+
return urlparse(self.connection_string).username or "postgres"
|
|
452
|
+
|
|
453
|
+
@property
|
|
454
|
+
def password(self) -> str | None:
|
|
455
|
+
from urllib.parse import urlparse
|
|
456
|
+
return urlparse(self.connection_string).password
|
|
457
|
+
|
|
458
|
+
@property
|
|
459
|
+
def database(self) -> str:
|
|
460
|
+
from urllib.parse import urlparse
|
|
461
|
+
return urlparse(self.connection_string).path.lstrip("/")
|
|
462
|
+
|
|
463
|
+
@property
|
|
464
|
+
def host(self) -> str:
|
|
465
|
+
from urllib.parse import urlparse
|
|
466
|
+
return urlparse(self.connection_string).hostname or "localhost"
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def port(self) -> int:
|
|
470
|
+
from urllib.parse import urlparse
|
|
471
|
+
return urlparse(self.connection_string).port or 5432
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class MigrationSettings(BaseSettings):
|
|
475
|
+
"""
|
|
476
|
+
Migration settings.
|
|
477
|
+
|
|
478
|
+
Environment variables:
|
|
479
|
+
MIGRATION__AUTO_UPGRADE - Automatically run migrations on startup
|
|
480
|
+
MIGRATION__MODE - Migration safety mode (permissive, additive, strict)
|
|
481
|
+
MIGRATION__ALLOW_DROP_COLUMNS - Allow DROP COLUMN operations
|
|
482
|
+
MIGRATION__ALLOW_DROP_TABLES - Allow DROP TABLE operations
|
|
483
|
+
MIGRATION__ALLOW_ALTER_COLUMNS - Allow ALTER COLUMN TYPE operations
|
|
484
|
+
MIGRATION__ALLOW_RENAME_COLUMNS - Allow RENAME COLUMN operations
|
|
485
|
+
MIGRATION__ALLOW_RENAME_TABLES - Allow RENAME TABLE operations
|
|
486
|
+
MIGRATION__UNSAFE_ALTER_WARNING - Warn on unsafe ALTER operations
|
|
487
|
+
"""
|
|
488
|
+
|
|
489
|
+
model_config = SettingsConfigDict(
|
|
490
|
+
env_prefix="MIGRATION__",
|
|
491
|
+
env_file=".env",
|
|
492
|
+
env_file_encoding="utf-8",
|
|
493
|
+
extra="ignore",
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
auto_upgrade: bool = Field(
|
|
497
|
+
default=True,
|
|
498
|
+
description="Automatically run database migrations on startup",
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
mode: str = Field(
|
|
502
|
+
default="permissive",
|
|
503
|
+
description="Migration safety mode: permissive, additive, strict",
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
allow_drop_columns: bool = Field(
|
|
507
|
+
default=False,
|
|
508
|
+
description="Allow DROP COLUMN operations (unsafe)",
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
allow_drop_tables: bool = Field(
|
|
512
|
+
default=False,
|
|
513
|
+
description="Allow DROP TABLE operations (unsafe)",
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
allow_alter_columns: bool = Field(
|
|
517
|
+
default=True,
|
|
518
|
+
description="Allow ALTER COLUMN TYPE operations (can be unsafe)",
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
allow_rename_columns: bool = Field(
|
|
522
|
+
default=True,
|
|
523
|
+
description="Allow RENAME COLUMN operations (can be unsafe)",
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
allow_rename_tables: bool = Field(
|
|
527
|
+
default=True,
|
|
528
|
+
description="Allow RENAME TABLE operations (can be unsafe)",
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
unsafe_alter_warning: bool = Field(
|
|
532
|
+
default=True,
|
|
533
|
+
description="Emit warning on potentially unsafe ALTER operations",
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
class StorageSettings(BaseSettings):
|
|
538
|
+
"""
|
|
539
|
+
Storage provider settings.
|
|
540
|
+
|
|
541
|
+
Controls which storage backend to use for file uploads and artifacts.
|
|
542
|
+
|
|
543
|
+
Environment variables:
|
|
544
|
+
STORAGE__PROVIDER - Storage provider (local or s3, default: local)
|
|
545
|
+
STORAGE__BASE_PATH - Base path for local filesystem storage (default: ~/.rem/fs)
|
|
546
|
+
"""
|
|
547
|
+
|
|
548
|
+
model_config = SettingsConfigDict(
|
|
549
|
+
env_prefix="STORAGE__",
|
|
550
|
+
env_file=".env",
|
|
551
|
+
env_file_encoding="utf-8",
|
|
552
|
+
extra="ignore",
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
provider: str = Field(
|
|
556
|
+
default="local",
|
|
557
|
+
description="Storage provider: 'local' for filesystem, 's3' for AWS S3",
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
base_path: str = Field(
|
|
561
|
+
default="~/.rem/fs",
|
|
562
|
+
description="Base path for local filesystem storage (only used when provider='local')",
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
class S3Settings(BaseSettings):
|
|
567
|
+
"""
|
|
568
|
+
S3 storage settings for file uploads and artifacts.
|
|
569
|
+
|
|
570
|
+
Uses IRSA (IAM Roles for Service Accounts) for AWS permissions in EKS.
|
|
571
|
+
For local development, can use MinIO or provide access keys.
|
|
572
|
+
|
|
573
|
+
Bucket Naming Convention:
|
|
574
|
+
- Default: rem-io-{environment} (e.g., rem-io-development, rem-io-staging, rem-io-production)
|
|
575
|
+
- Matches Kubernetes manifest convention for consistency
|
|
576
|
+
- Override with S3__BUCKET_NAME environment variable
|
|
577
|
+
|
|
578
|
+
Path Convention:
|
|
579
|
+
Uploads: s3://{bucket}/{version}/uploads/{user_id}/{yyyy}/{mm}/{dd}/{filename}
|
|
580
|
+
Parsed: s3://{bucket}/{version}/parsed/{user_id}/{yyyy}/{mm}/{dd}/{filename}/{resource}
|
|
581
|
+
|
|
582
|
+
Environment variables:
|
|
583
|
+
S3__BUCKET_NAME - S3 bucket name (default: rem-io-development)
|
|
584
|
+
S3__VERSION - API version for paths (default: v1)
|
|
585
|
+
S3__UPLOADS_PREFIX - Uploads directory prefix (default: uploads)
|
|
586
|
+
S3__PARSED_PREFIX - Parsed content directory prefix (default: parsed)
|
|
587
|
+
S3__REGION - AWS region
|
|
588
|
+
S3__ENDPOINT_URL - Custom endpoint (for MinIO, LocalStack)
|
|
589
|
+
S3__ACCESS_KEY_ID - AWS access key (not needed with IRSA)
|
|
590
|
+
S3__SECRET_ACCESS_KEY - AWS secret key (not needed with IRSA)
|
|
591
|
+
S3__USE_SSL - Use SSL for connections (default: true)
|
|
592
|
+
"""
|
|
593
|
+
|
|
594
|
+
model_config = SettingsConfigDict(
|
|
595
|
+
env_prefix="S3__",
|
|
596
|
+
env_file=".env",
|
|
597
|
+
env_file_encoding="utf-8",
|
|
598
|
+
extra="ignore",
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
bucket_name: str = Field(
|
|
602
|
+
default="rem-io-development",
|
|
603
|
+
description="S3 bucket name (convention: rem-io-{environment})",
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
version: str = Field(
|
|
607
|
+
default="v1",
|
|
608
|
+
description="API version for S3 path structure",
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
uploads_prefix: str = Field(
|
|
612
|
+
default="uploads",
|
|
613
|
+
description="Prefix for uploaded files (e.g., 'uploads' -> bucket/v1/uploads/...)",
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
parsed_prefix: str = Field(
|
|
617
|
+
default="parsed",
|
|
618
|
+
description="Prefix for parsed content (e.g., 'parsed' -> bucket/v1/parsed/...)",
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
region: str = Field(
|
|
622
|
+
default="us-east-1",
|
|
623
|
+
description="AWS region",
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
endpoint_url: str | None = Field(
|
|
627
|
+
default=None,
|
|
628
|
+
description="Custom S3 endpoint (for MinIO, LocalStack)",
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
access_key_id: str | None = Field(
|
|
632
|
+
default=None,
|
|
633
|
+
description="AWS access key ID (not needed with IRSA in EKS)",
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
secret_access_key: str | None = Field(
|
|
637
|
+
default=None,
|
|
638
|
+
description="AWS secret access key (not needed with IRSA in EKS)",
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
use_ssl: bool = Field(
|
|
642
|
+
default=True,
|
|
643
|
+
description="Use SSL for S3 connections",
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
class ChunkingSettings(BaseSettings):
|
|
648
|
+
"""
|
|
649
|
+
Document chunking settings for semantic text splitting.
|
|
650
|
+
|
|
651
|
+
Uses semchunk for semantic-aware text chunking that respects document structure.
|
|
652
|
+
Generous chunk sizes (couple paragraphs) with reasonable overlaps for context.
|
|
653
|
+
|
|
654
|
+
Environment variables:
|
|
655
|
+
CHUNKING__CHUNK_SIZE - Target chunk size in characters
|
|
656
|
+
CHUNKING__OVERLAP - Overlap between chunks in characters
|
|
657
|
+
CHUNKING__MIN_CHUNK_SIZE - Minimum chunk size (avoid tiny chunks)
|
|
658
|
+
CHUNKING__MAX_CHUNK_SIZE - Maximum chunk size (hard limit)
|
|
659
|
+
"""
|
|
660
|
+
|
|
661
|
+
model_config = SettingsConfigDict(
|
|
662
|
+
env_prefix="CHUNKING__",
|
|
663
|
+
env_file=".env",
|
|
664
|
+
env_file_encoding="utf-8",
|
|
665
|
+
extra="ignore",
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
chunk_size: int = Field(
|
|
669
|
+
default=1500,
|
|
670
|
+
description="Target chunk size in characters (couple paragraphs, ~300-400 words)",
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
overlap: int = Field(
|
|
674
|
+
default=200,
|
|
675
|
+
description="Overlap between chunks in characters for context preservation",
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
min_chunk_size: int = Field(
|
|
679
|
+
default=100,
|
|
680
|
+
description="Minimum chunk size to avoid tiny fragments",
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
max_chunk_size: int = Field(
|
|
684
|
+
default=2500,
|
|
685
|
+
description="Maximum chunk size (hard limit, prevents oversized chunks)",
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
class ContentSettings(BaseSettings):
|
|
690
|
+
"""
|
|
691
|
+
Content provider settings for file processing.
|
|
692
|
+
|
|
693
|
+
Defines supported file types for each provider type.
|
|
694
|
+
Allows override of specific extensions via register_provider().
|
|
695
|
+
|
|
696
|
+
Environment variables:
|
|
697
|
+
CONTENT__SUPPORTED_TEXT_TYPES - Comma-separated text extensions
|
|
698
|
+
CONTENT__SUPPORTED_DOC_TYPES - Comma-separated document extensions
|
|
699
|
+
CONTENT__SUPPORTED_AUDIO_TYPES - Comma-separated audio extensions
|
|
700
|
+
CONTENT__SUPPORTED_IMAGE_TYPES - Comma-separated image extensions
|
|
701
|
+
CONTENT__IMAGE_VLLM_SAMPLE_RATE - Sampling rate for vision LLM analysis (0.0-1.0)
|
|
702
|
+
CONTENT__IMAGE_VLLM_PROVIDER - Vision provider (anthropic, gemini, openai)
|
|
703
|
+
CONTENT__IMAGE_VLLM_MODEL - Vision model name (provider default if not set)
|
|
704
|
+
CONTENT__CLIP_PROVIDER - CLIP embedding provider (jina, self-hosted)
|
|
705
|
+
CONTENT__CLIP_MODEL - CLIP model name (jina-clip-v1, jina-clip-v2)
|
|
706
|
+
CONTENT__JINA_API_KEY - Jina AI API key for CLIP embeddings
|
|
707
|
+
"""
|
|
708
|
+
|
|
709
|
+
model_config = SettingsConfigDict(
|
|
710
|
+
env_prefix="CONTENT__",
|
|
711
|
+
env_file=".env",
|
|
712
|
+
env_file_encoding="utf-8",
|
|
713
|
+
extra="ignore",
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
supported_text_types: list[str] = Field(
|
|
717
|
+
default_factory=lambda: [
|
|
718
|
+
# Plain text
|
|
719
|
+
".txt",
|
|
720
|
+
".md",
|
|
721
|
+
".markdown",
|
|
722
|
+
# Data formats
|
|
723
|
+
".json",
|
|
724
|
+
".yaml",
|
|
725
|
+
".yml",
|
|
726
|
+
".csv",
|
|
727
|
+
".tsv",
|
|
728
|
+
".log",
|
|
729
|
+
# Code files
|
|
730
|
+
".py",
|
|
731
|
+
".js",
|
|
732
|
+
".ts",
|
|
733
|
+
".tsx",
|
|
734
|
+
".jsx",
|
|
735
|
+
".java",
|
|
736
|
+
".c",
|
|
737
|
+
".cpp",
|
|
738
|
+
".h",
|
|
739
|
+
".rs",
|
|
740
|
+
".go",
|
|
741
|
+
".rb",
|
|
742
|
+
".php",
|
|
743
|
+
".sh",
|
|
744
|
+
".bash",
|
|
745
|
+
".sql",
|
|
746
|
+
# Web files
|
|
747
|
+
".html",
|
|
748
|
+
".css",
|
|
749
|
+
".xml",
|
|
750
|
+
],
|
|
751
|
+
description="File extensions handled by TextProvider (plain text, code, data files)",
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
supported_doc_types: list[str] = Field(
|
|
755
|
+
default_factory=lambda: [
|
|
756
|
+
# Documents
|
|
757
|
+
".pdf",
|
|
758
|
+
".docx",
|
|
759
|
+
".pptx",
|
|
760
|
+
".xlsx",
|
|
761
|
+
# Images (OCR text extraction)
|
|
762
|
+
".png",
|
|
763
|
+
".jpg",
|
|
764
|
+
".jpeg",
|
|
765
|
+
],
|
|
766
|
+
description="File extensions handled by DocProvider (Kreuzberg: PDFs, Office docs, images with OCR)",
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
supported_audio_types: list[str] = Field(
|
|
770
|
+
default_factory=lambda: [
|
|
771
|
+
".wav",
|
|
772
|
+
".mp3",
|
|
773
|
+
".m4a",
|
|
774
|
+
".flac",
|
|
775
|
+
".ogg",
|
|
776
|
+
],
|
|
777
|
+
description="File extensions handled by AudioProvider (Whisper API transcription)",
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
supported_image_types: list[str] = Field(
|
|
781
|
+
default_factory=lambda: [
|
|
782
|
+
".png",
|
|
783
|
+
".jpg",
|
|
784
|
+
".jpeg",
|
|
785
|
+
".gif",
|
|
786
|
+
".webp",
|
|
787
|
+
],
|
|
788
|
+
description="File extensions handled by ImageProvider (vision LLM + CLIP embeddings)",
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
image_vllm_sample_rate: float = Field(
|
|
792
|
+
default=0.0,
|
|
793
|
+
ge=0.0,
|
|
794
|
+
le=1.0,
|
|
795
|
+
description="Sampling rate for vision LLM analysis (0.0 = never, 1.0 = always, 0.1 = 10% of images). Gold tier users always get vision analysis.",
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
image_vllm_provider: str = Field(
|
|
799
|
+
default="anthropic",
|
|
800
|
+
description="Vision LLM provider: anthropic, gemini, or openai",
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
image_vllm_model: str | None = Field(
|
|
804
|
+
default=None,
|
|
805
|
+
description="Vision model name (uses provider default if None)",
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
clip_provider: str = Field(
|
|
809
|
+
default="jina",
|
|
810
|
+
description="CLIP embedding provider (jina for API, self-hosted for future KEDA pods)",
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
clip_model: str = Field(
|
|
814
|
+
default="jina-clip-v2",
|
|
815
|
+
description="CLIP model for image embeddings (jina-clip-v1, jina-clip-v2, or custom)",
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
jina_api_key: str | None = Field(
|
|
819
|
+
default=None,
|
|
820
|
+
description="Jina AI API key for CLIP embeddings (https://jina.ai/embeddings/)",
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
class SQSSettings(BaseSettings):
|
|
825
|
+
"""
|
|
826
|
+
SQS queue settings for file processing.
|
|
827
|
+
|
|
828
|
+
Uses IRSA (IAM Roles for Service Accounts) for AWS permissions in EKS.
|
|
829
|
+
For local development, can use access keys.
|
|
830
|
+
|
|
831
|
+
Environment variables:
|
|
832
|
+
SQS__QUEUE_URL - SQS queue URL (from Pulumi output)
|
|
833
|
+
SQS__REGION - AWS region
|
|
834
|
+
SQS__MAX_MESSAGES - Max messages per receive (1-10)
|
|
835
|
+
SQS__WAIT_TIME_SECONDS - Long polling wait time
|
|
836
|
+
SQS__VISIBILITY_TIMEOUT - Message visibility timeout
|
|
837
|
+
"""
|
|
838
|
+
|
|
839
|
+
model_config = SettingsConfigDict(
|
|
840
|
+
env_prefix="SQS__",
|
|
841
|
+
env_file=".env",
|
|
842
|
+
env_file_encoding="utf-8",
|
|
843
|
+
extra="ignore",
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
queue_url: str = Field(
|
|
847
|
+
default="",
|
|
848
|
+
description="SQS queue URL for file processing events",
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
region: str = Field(
|
|
852
|
+
default="us-east-1",
|
|
853
|
+
description="AWS region",
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
max_messages: int = Field(
|
|
857
|
+
default=10,
|
|
858
|
+
ge=1,
|
|
859
|
+
le=10,
|
|
860
|
+
description="Maximum messages to receive per batch (1-10)",
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
wait_time_seconds: int = Field(
|
|
864
|
+
default=20,
|
|
865
|
+
ge=0,
|
|
866
|
+
le=20,
|
|
867
|
+
description="Long polling wait time in seconds (0-20, 20 recommended)",
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
visibility_timeout: int = Field(
|
|
871
|
+
default=300,
|
|
872
|
+
description="Visibility timeout in seconds (should match processing time)",
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
class ChatSettings(BaseSettings):
|
|
877
|
+
"""
|
|
878
|
+
Chat and session context settings.
|
|
879
|
+
|
|
880
|
+
Environment variables:
|
|
881
|
+
CHAT__AUTO_INJECT_USER_CONTEXT - Automatically inject user profile into every request (default: false)
|
|
882
|
+
|
|
883
|
+
Design Philosophy:
|
|
884
|
+
- Session history is ALWAYS loaded (required for multi-turn conversations)
|
|
885
|
+
- Compression with REM LOOKUP hints keeps session history efficient
|
|
886
|
+
- User context is on-demand by default (agents receive REM LOOKUP hints)
|
|
887
|
+
- When auto_inject_user_context enabled, user profile is loaded and injected
|
|
888
|
+
|
|
889
|
+
Session History (always loaded with compression):
|
|
890
|
+
- Each chat request is a single message, so history MUST be recovered
|
|
891
|
+
- Long assistant responses stored as separate Message entities
|
|
892
|
+
- Compressed versions include REM LOOKUP hints: "... [REM LOOKUP session-{id}-msg-{index}] ..."
|
|
893
|
+
- Agent can retrieve full content on-demand using REM LOOKUP
|
|
894
|
+
- Prevents context window bloat while maintaining conversation continuity
|
|
895
|
+
|
|
896
|
+
User Context (on-demand by default):
|
|
897
|
+
- Agent system prompt includes: "User ID: {user_id}. To load user profile: Use REM LOOKUP users/{user_id}"
|
|
898
|
+
- Agent decides whether to load profile based on query
|
|
899
|
+
- More efficient for queries that don't need personalization
|
|
900
|
+
|
|
901
|
+
User Context (auto-inject when enabled):
|
|
902
|
+
- Set CHAT__AUTO_INJECT_USER_CONTEXT=true
|
|
903
|
+
- User profile automatically loaded and injected into system message
|
|
904
|
+
- Simpler for basic chatbots that always need context
|
|
905
|
+
"""
|
|
906
|
+
|
|
907
|
+
model_config = SettingsConfigDict(
|
|
908
|
+
env_prefix="CHAT__",
|
|
909
|
+
env_file=".env",
|
|
910
|
+
env_file_encoding="utf-8",
|
|
911
|
+
extra="ignore",
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
auto_inject_user_context: bool = Field(
|
|
915
|
+
default=False,
|
|
916
|
+
description="Automatically inject user profile into every request (default: false, use REM LOOKUP hint instead)",
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
class APISettings(BaseSettings):
|
|
921
|
+
"""
|
|
922
|
+
API server settings.
|
|
923
|
+
|
|
924
|
+
Environment variables:
|
|
925
|
+
API__HOST - Host to bind to (0.0.0.0 for Docker, 127.0.0.1 for local)
|
|
926
|
+
API__PORT - Port to listen on
|
|
927
|
+
API__RELOAD - Enable auto-reload for development
|
|
928
|
+
API__WORKERS - Number of worker processes (production)
|
|
929
|
+
API__LOG_LEVEL - Logging level (debug, info, warning, error)
|
|
930
|
+
"""
|
|
931
|
+
|
|
932
|
+
model_config = SettingsConfigDict(
|
|
933
|
+
env_prefix="API__",
|
|
934
|
+
env_file=".env",
|
|
935
|
+
env_file_encoding="utf-8",
|
|
936
|
+
extra="ignore",
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
host: str = Field(
|
|
940
|
+
default="0.0.0.0",
|
|
941
|
+
description="Host to bind to (0.0.0.0 for Docker, 127.0.0.1 for local only)",
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
port: int = Field(
|
|
945
|
+
default=8000,
|
|
946
|
+
description="Port to listen on",
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
reload: bool = Field(
|
|
950
|
+
default=True,
|
|
951
|
+
description="Enable auto-reload for development (disable in production)",
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
workers: int = Field(
|
|
955
|
+
default=1,
|
|
956
|
+
description="Number of worker processes (use >1 in production)",
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
log_level: str = Field(
|
|
960
|
+
default="info",
|
|
961
|
+
description="Logging level (debug, info, warning, error, critical)",
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
class GitSettings(BaseSettings):
|
|
966
|
+
"""
|
|
967
|
+
Git repository provider settings for versioned schema/experiment syncing.
|
|
968
|
+
|
|
969
|
+
Enables syncing of agent schemas, evaluators, and experiments from Git repositories
|
|
970
|
+
using either SSH or HTTPS authentication. Designed for cluster environments where
|
|
971
|
+
secrets are provided via Kubernetes Secrets or similar mechanisms.
|
|
972
|
+
|
|
973
|
+
**Use Cases**:
|
|
974
|
+
- Sync agent schemas from versioned repos (repo/schemas/)
|
|
975
|
+
- Sync experiments and evaluation datasets (repo/experiments/)
|
|
976
|
+
- Clone specific tags/releases for reproducible evaluations
|
|
977
|
+
- Support multi-tenancy with per-tenant repo configurations
|
|
978
|
+
|
|
979
|
+
**Authentication Methods**:
|
|
980
|
+
1. **SSH** (recommended for production):
|
|
981
|
+
- Uses SSH keys from filesystem or Kubernetes Secrets
|
|
982
|
+
- Path specified via GIT__SSH_KEY_PATH or mounted at /etc/git-secret/ssh
|
|
983
|
+
- Known hosts file at /etc/git-secret/known_hosts
|
|
984
|
+
|
|
985
|
+
2. **HTTPS with Personal Access Token** (PAT):
|
|
986
|
+
- GitHub: 5,000 API requests/hour per authenticated user
|
|
987
|
+
- GitLab: Similar rate limits
|
|
988
|
+
- Store PAT in GIT__PERSONAL_ACCESS_TOKEN environment variable
|
|
989
|
+
|
|
990
|
+
**Kubernetes Deployment Pattern** (git-sync sidecar):
|
|
991
|
+
```yaml
|
|
992
|
+
# Secret creation (one-time setup)
|
|
993
|
+
kubectl create secret generic git-creds \\
|
|
994
|
+
--from-file=ssh=$HOME/.ssh/id_rsa \\
|
|
995
|
+
--from-file=known_hosts=$HOME/.ssh/known_hosts
|
|
996
|
+
|
|
997
|
+
# Pod spec with secret mounting
|
|
998
|
+
volumes:
|
|
999
|
+
- name: git-secret
|
|
1000
|
+
secret:
|
|
1001
|
+
secretName: git-creds
|
|
1002
|
+
defaultMode: 0400 # Read-only for owner
|
|
1003
|
+
containers:
|
|
1004
|
+
- name: rem-api
|
|
1005
|
+
volumeMounts:
|
|
1006
|
+
- name: git-secret
|
|
1007
|
+
mountPath: /etc/git-secret
|
|
1008
|
+
readOnly: true
|
|
1009
|
+
securityContext:
|
|
1010
|
+
fsGroup: 65533 # Make secrets readable by git user
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
**Path Conventions**:
|
|
1014
|
+
- Agent schemas: {repo_root}/schemas/
|
|
1015
|
+
- Experiments: {repo_root}/experiments/
|
|
1016
|
+
- Evaluators: {repo_root}/schemas/evaluators/
|
|
1017
|
+
|
|
1018
|
+
**Performance & Caching**:
|
|
1019
|
+
- Clones cached locally in {cache_dir}/{repo_hash}/
|
|
1020
|
+
- Supports shallow clones (--depth=1) for faster syncing
|
|
1021
|
+
- Periodic refresh via cron jobs or git-sync sidecar
|
|
1022
|
+
|
|
1023
|
+
Environment variables:
|
|
1024
|
+
GIT__ENABLED - Enable Git provider (default: False)
|
|
1025
|
+
GIT__DEFAULT_REPO_URL - Default Git repository URL (ssh:// or https://)
|
|
1026
|
+
GIT__DEFAULT_BRANCH - Default branch to clone (default: main)
|
|
1027
|
+
GIT__SSH_KEY_PATH - Path to SSH private key (default: /etc/git-secret/ssh)
|
|
1028
|
+
GIT__KNOWN_HOSTS_PATH - Path to known_hosts file (default: /etc/git-secret/known_hosts)
|
|
1029
|
+
GIT__PERSONAL_ACCESS_TOKEN - GitHub/GitLab PAT for HTTPS auth
|
|
1030
|
+
GIT__CACHE_DIR - Local cache directory for cloned repos
|
|
1031
|
+
GIT__SHALLOW_CLONE - Use shallow clone (--depth=1) for faster sync
|
|
1032
|
+
GIT__VERIFY_SSL - Verify SSL certificates for HTTPS (default: True)
|
|
1033
|
+
|
|
1034
|
+
**Security Best Practices**:
|
|
1035
|
+
- Store SSH keys in Kubernetes Secrets, never in environment variables
|
|
1036
|
+
- Use read-only SSH keys (deploy keys) with minimal permissions
|
|
1037
|
+
- Enable known_hosts verification to prevent MITM attacks
|
|
1038
|
+
- Rotate PATs regularly (90-day expiration recommended)
|
|
1039
|
+
- Use IRSA/Workload Identity for cloud-provider Git services
|
|
1040
|
+
"""
|
|
1041
|
+
|
|
1042
|
+
model_config = SettingsConfigDict(
|
|
1043
|
+
env_prefix="GIT__",
|
|
1044
|
+
env_file=".env",
|
|
1045
|
+
env_file_encoding="utf-8",
|
|
1046
|
+
extra="ignore",
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
enabled: bool = Field(
|
|
1050
|
+
default=False,
|
|
1051
|
+
description="Enable Git provider for syncing schemas/experiments from Git repos",
|
|
1052
|
+
)
|
|
1053
|
+
|
|
1054
|
+
default_repo_url: str | None = Field(
|
|
1055
|
+
default=None,
|
|
1056
|
+
description="Default Git repository URL (ssh://git@github.com/org/repo.git or https://github.com/org/repo.git)",
|
|
1057
|
+
)
|
|
1058
|
+
|
|
1059
|
+
default_branch: str = Field(
|
|
1060
|
+
default="main",
|
|
1061
|
+
description="Default branch to clone/checkout (main, master, develop, etc.)",
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
ssh_key_path: str = Field(
|
|
1065
|
+
default="/etc/git-secret/ssh",
|
|
1066
|
+
description="Path to SSH private key (Kubernetes Secret mount point or local path)",
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
known_hosts_path: str = Field(
|
|
1070
|
+
default="/etc/git-secret/known_hosts",
|
|
1071
|
+
description="Path to known_hosts file for SSH host verification",
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
personal_access_token: str | None = Field(
|
|
1075
|
+
default=None,
|
|
1076
|
+
description="Personal Access Token (PAT) for HTTPS authentication (GitHub, GitLab, etc.)",
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
cache_dir: str = Field(
|
|
1080
|
+
default="/tmp/rem-git-cache",
|
|
1081
|
+
description="Local cache directory for cloned repositories",
|
|
1082
|
+
)
|
|
1083
|
+
|
|
1084
|
+
shallow_clone: bool = Field(
|
|
1085
|
+
default=True,
|
|
1086
|
+
description="Use shallow clone (--depth=1) for faster syncing (recommended for large repos)",
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
verify_ssl: bool = Field(
|
|
1090
|
+
default=True,
|
|
1091
|
+
description="Verify SSL certificates for HTTPS connections (disable for self-signed certs)",
|
|
1092
|
+
)
|
|
1093
|
+
|
|
1094
|
+
sync_interval: int = Field(
|
|
1095
|
+
default=300,
|
|
1096
|
+
description="Sync interval in seconds for git-sync sidecar pattern (default: 5 minutes)",
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
class TestSettings(BaseSettings):
|
|
1101
|
+
"""
|
|
1102
|
+
Test environment settings.
|
|
1103
|
+
|
|
1104
|
+
Environment variables:
|
|
1105
|
+
TEST__USER_EMAIL - Test user email (default: test@rem.ai)
|
|
1106
|
+
TEST__USER_ID - Test user UUID (auto-generated from email if not provided)
|
|
1107
|
+
|
|
1108
|
+
The user_id is a deterministic UUID v5 generated from the email address.
|
|
1109
|
+
This ensures consistent IDs across test runs and allows tests to use both
|
|
1110
|
+
email and UUID interchangeably.
|
|
1111
|
+
"""
|
|
1112
|
+
|
|
1113
|
+
model_config = SettingsConfigDict(
|
|
1114
|
+
env_prefix="TEST__",
|
|
1115
|
+
env_file=".env",
|
|
1116
|
+
env_file_encoding="utf-8",
|
|
1117
|
+
extra="ignore",
|
|
1118
|
+
)
|
|
1119
|
+
|
|
1120
|
+
user_email: str = Field(
|
|
1121
|
+
default="test@rem.ai",
|
|
1122
|
+
description="Test user email address",
|
|
1123
|
+
)
|
|
1124
|
+
|
|
1125
|
+
user_id: str | None = Field(
|
|
1126
|
+
default=None,
|
|
1127
|
+
description="Test user UUID (auto-generated from email if not provided)",
|
|
1128
|
+
)
|
|
1129
|
+
|
|
1130
|
+
@property
|
|
1131
|
+
def effective_user_id(self) -> str:
|
|
1132
|
+
"""
|
|
1133
|
+
Get the effective user ID (either explicit or generated from email).
|
|
1134
|
+
|
|
1135
|
+
Returns a deterministic UUID v5 based on the email address if user_id
|
|
1136
|
+
is not explicitly set. This ensures consistent test data across runs.
|
|
1137
|
+
"""
|
|
1138
|
+
if self.user_id:
|
|
1139
|
+
return self.user_id
|
|
1140
|
+
|
|
1141
|
+
# Generate deterministic UUID v5 from email
|
|
1142
|
+
# Using DNS namespace as the base (standard practice for email-based UUIDs)
|
|
1143
|
+
import uuid
|
|
1144
|
+
return str(uuid.uuid5(uuid.NAMESPACE_DNS, self.user_email))
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
class Settings(BaseSettings):
|
|
1148
|
+
"""
|
|
1149
|
+
Global application settings.
|
|
1150
|
+
|
|
1151
|
+
Aggregates all nested settings groups with environment variable support.
|
|
1152
|
+
Uses double underscore delimiter for nested variables (LLM__DEFAULT_MODEL).
|
|
1153
|
+
|
|
1154
|
+
Environment variables:
|
|
1155
|
+
TEAM - Team/project name for observability
|
|
1156
|
+
ENVIRONMENT - Environment (development, staging, production)
|
|
1157
|
+
DOMAIN - Public domain for OAuth discovery
|
|
1158
|
+
ROOT_PATH - Root path for reverse proxy (e.g., /rem for ALB routing)
|
|
1159
|
+
TEST__USER_ID - Default user ID for integration tests
|
|
1160
|
+
"""
|
|
1161
|
+
|
|
1162
|
+
model_config = SettingsConfigDict(
|
|
1163
|
+
env_file=".env",
|
|
1164
|
+
env_file_encoding="utf-8",
|
|
1165
|
+
env_nested_delimiter="__",
|
|
1166
|
+
extra="ignore",
|
|
1167
|
+
)
|
|
1168
|
+
|
|
1169
|
+
team: str = Field(
|
|
1170
|
+
default="rem",
|
|
1171
|
+
description="Team or project name for observability",
|
|
1172
|
+
)
|
|
1173
|
+
|
|
1174
|
+
environment: str = Field(
|
|
1175
|
+
default="development",
|
|
1176
|
+
description="Environment (development, staging, production)",
|
|
1177
|
+
)
|
|
1178
|
+
|
|
1179
|
+
domain: str | None = Field(
|
|
1180
|
+
default=None,
|
|
1181
|
+
description="Public domain for OAuth discovery (e.g., https://api.example.com)",
|
|
1182
|
+
)
|
|
1183
|
+
|
|
1184
|
+
root_path: str = Field(
|
|
1185
|
+
default="",
|
|
1186
|
+
description="Root path for reverse proxy (e.g., /rem for ALB routing)",
|
|
1187
|
+
)
|
|
1188
|
+
|
|
1189
|
+
sql_dir: str = Field(
|
|
1190
|
+
default="src/rem/sql",
|
|
1191
|
+
description="Directory for SQL files and migrations",
|
|
1192
|
+
)
|
|
1193
|
+
|
|
1194
|
+
# Nested settings groups
|
|
1195
|
+
api: APISettings = Field(default_factory=APISettings)
|
|
1196
|
+
chat: ChatSettings = Field(default_factory=ChatSettings)
|
|
1197
|
+
llm: LLMSettings = Field(default_factory=LLMSettings)
|
|
1198
|
+
mcp: MCPSettings = Field(default_factory=MCPSettings)
|
|
1199
|
+
otel: OTELSettings = Field(default_factory=OTELSettings)
|
|
1200
|
+
phoenix: PhoenixSettings = Field(default_factory=PhoenixSettings)
|
|
1201
|
+
auth: AuthSettings = Field(default_factory=AuthSettings)
|
|
1202
|
+
postgres: PostgresSettings = Field(default_factory=PostgresSettings)
|
|
1203
|
+
migration: MigrationSettings = Field(default_factory=MigrationSettings)
|
|
1204
|
+
storage: StorageSettings = Field(default_factory=StorageSettings)
|
|
1205
|
+
s3: S3Settings = Field(default_factory=S3Settings)
|
|
1206
|
+
git: GitSettings = Field(default_factory=GitSettings)
|
|
1207
|
+
sqs: SQSSettings = Field(default_factory=SQSSettings)
|
|
1208
|
+
chunking: ChunkingSettings = Field(default_factory=ChunkingSettings)
|
|
1209
|
+
content: ContentSettings = Field(default_factory=ContentSettings)
|
|
1210
|
+
test: TestSettings = Field(default_factory=TestSettings)
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
# Load configuration from ~/.rem/config.yaml before initializing settings
|
|
1214
|
+
# This allows user configuration to be merged with environment variables
|
|
1215
|
+
try:
|
|
1216
|
+
from rem.config import load_config, merge_config_to_env
|
|
1217
|
+
|
|
1218
|
+
_config = load_config()
|
|
1219
|
+
if _config:
|
|
1220
|
+
merge_config_to_env(_config)
|
|
1221
|
+
except ImportError:
|
|
1222
|
+
# config module not available (e.g., during initial setup)
|
|
1223
|
+
pass
|
|
1224
|
+
|
|
1225
|
+
# Global settings singleton
|
|
1226
|
+
settings = Settings()
|
|
1227
|
+
|
|
1228
|
+
# Sync API keys to environment for pydantic-ai
|
|
1229
|
+
# Pydantic AI providers check environment directly, so we need to ensure
|
|
1230
|
+
# API keys from settings (LLM__*_API_KEY) are also available without prefix
|
|
1231
|
+
if settings.llm.openai_api_key and not os.getenv("OPENAI_API_KEY"):
|
|
1232
|
+
os.environ["OPENAI_API_KEY"] = settings.llm.openai_api_key
|
|
1233
|
+
|
|
1234
|
+
if settings.llm.anthropic_api_key and not os.getenv("ANTHROPIC_API_KEY"):
|
|
1235
|
+
os.environ["ANTHROPIC_API_KEY"] = settings.llm.anthropic_api_key
|