or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mddatamodel.mdevaluation.mdfine-tuning.mdindex.mdmodels.mdprompts.mdrag-embeddings.mdtask-execution.mdtools.md

configuration.mddocs/

0

# Configuration and Utilities

1

2

Configuration management, formatting utilities, and async lock management for the Kiln AI system. Provides centralized configuration storage, helper functions, and concurrency utilities.

3

4

## Capabilities

5

6

### Configuration Management

7

8

Centralized configuration for Kiln AI with singleton access pattern.

9

10

```python { .api }

11

from kiln_ai.utils.config import Config, ConfigProperty, MCP_SECRETS_KEY

12

13

class Config:

14

"""

15

Configuration management for Kiln AI.

16

17

Stores configuration in ~/.kiln_settings/config.yaml

18

19

Properties:

20

- custom_models (list[str]): Custom model identifiers in format "provider::model"

21

- openai_compatible_providers (list[dict]): OpenAI-compatible provider configs

22

"""

23

24

@classmethod

25

def shared(cls) -> 'Config':

26

"""

27

Get singleton configuration instance.

28

29

Returns:

30

Config: Shared configuration instance

31

"""

32

33

def save(self) -> None:

34

"""

35

Save configuration to disk.

36

37

Writes to ~/.kiln_settings/config.yaml

38

"""

39

40

def load(self) -> None:

41

"""

42

Load configuration from disk.

43

44

Reads from ~/.kiln_settings/config.yaml

45

"""

46

47

def get(self, key: str, default=None):

48

"""

49

Get configuration value.

50

51

Parameters:

52

- key (str): Configuration key

53

- default: Default value if key not found

54

55

Returns:

56

Any: Configuration value or default

57

"""

58

59

def set(self, key: str, value) -> None:

60

"""

61

Set configuration value.

62

63

Parameters:

64

- key (str): Configuration key

65

- value: Value to set

66

"""

67

68

class ConfigProperty:

69

"""

70

Configuration property definition.

71

72

Properties:

73

- key (str): Property key

74

- default: Default value

75

- description (str): Property description

76

"""

77

78

# MCP secrets configuration key

79

MCP_SECRETS_KEY = "mcp_secrets"

80

```

81

82

### Async Lock Management

83

84

Manage async locks for concurrent operations.

85

86

```python { .api }

87

from kiln_ai.utils.lock import AsyncLockManager, shared_async_lock_manager

88

89

class AsyncLockManager:

90

"""

91

Manage async locks for concurrency control.

92

93

Methods:

94

- acquire(): Acquire a lock

95

- release(): Release a lock

96

- with_lock(): Context manager for lock

97

"""

98

99

async def acquire(self, lock_id: str) -> None:

100

"""

101

Acquire a lock.

102

103

Parameters:

104

- lock_id (str): Lock identifier

105

106

Blocks until lock is available.

107

"""

108

109

async def release(self, lock_id: str) -> None:

110

"""

111

Release a lock.

112

113

Parameters:

114

- lock_id (str): Lock identifier

115

"""

116

117

async def with_lock(self, lock_id: str):

118

"""

119

Context manager for lock acquisition.

120

121

Parameters:

122

- lock_id (str): Lock identifier

123

124

Usage:

125

async with lock_manager.with_lock("my_lock"):

126

# Critical section

127

pass

128

"""

129

130

# Shared lock manager singleton

131

shared_async_lock_manager = AsyncLockManager()

132

```

133

134

### Formatting Utilities

135

136

String formatting and conversion utilities.

137

138

```python { .api }

139

from kiln_ai.utils.formatting import snake_case

140

141

def snake_case(text: str) -> str:

142

"""

143

Convert string to snake_case.

144

145

Parameters:

146

- text (str): Input text (can be camelCase, PascalCase, or mixed)

147

148

Returns:

149

str: snake_case formatted string

150

151

Examples:

152

- "HelloWorld" -> "hello_world"

153

- "camelCase" -> "camel_case"

154

- "already_snake" -> "already_snake"

155

"""

156

```

157

158

## Usage Examples

159

160

### Basic Configuration

161

162

```python

163

from kiln_ai.utils.config import Config

164

165

# Get shared configuration instance

166

config = Config.shared()

167

168

# Access configuration values

169

custom_models = config.custom_models or []

170

print(f"Custom models: {custom_models}")

171

172

providers = config.openai_compatible_providers or []

173

print(f"Custom providers: {len(providers)}")

174

175

# Save configuration

176

config.save()

177

```

178

179

### Adding Custom Models

180

181

```python

182

from kiln_ai.utils.config import Config

183

184

config = Config.shared()

185

186

# Add custom model

187

new_model = "openai::gpt-3.5-turbo-custom"

188

custom_models = config.custom_models or []

189

190

if new_model not in custom_models:

191

custom_models.append(new_model)

192

config.custom_models = custom_models

193

config.save()

194

print(f"Added custom model: {new_model}")

195

196

# List all custom models

197

print("\nCustom models:")

198

for model in config.custom_models:

199

print(f" - {model}")

200

```

201

202

### Adding OpenAI Compatible Provider

203

204

```python

205

from kiln_ai.utils.config import Config

206

207

config = Config.shared()

208

209

# Add custom provider

210

provider_config = {

211

"name": "CustomOllama",

212

"base_url": "http://localhost:11434/v1",

213

"api_key": "ollama"

214

}

215

216

providers = config.openai_compatible_providers or []

217

218

# Check if provider already exists

219

existing = next((p for p in providers if p["name"] == provider_config["name"]), None)

220

221

if not existing:

222

providers.append(provider_config)

223

config.openai_compatible_providers = providers

224

config.save()

225

print(f"Added provider: {provider_config['name']}")

226

else:

227

print(f"Provider {provider_config['name']} already exists")

228

229

# List all providers

230

print("\nConfigured providers:")

231

for provider in config.openai_compatible_providers:

232

print(f" - {provider['name']}: {provider['base_url']}")

233

```

234

235

### Configuration with Environment Variables

236

237

```python

238

from kiln_ai.utils.config import Config

239

import os

240

241

config = Config.shared()

242

243

# Get API keys from environment

244

openai_key = os.getenv("OPENAI_API_KEY")

245

anthropic_key = os.getenv("ANTHROPIC_API_KEY")

246

247

# Store in config if not already present

248

if openai_key and not config.get("openai_api_key"):

249

config.set("openai_api_key", openai_key)

250

251

if anthropic_key and not config.get("anthropic_api_key"):

252

config.set("anthropic_api_key", anthropic_key)

253

254

config.save()

255

```

256

257

### MCP Secrets Management

258

259

```python

260

from kiln_ai.utils.config import Config, MCP_SECRETS_KEY

261

262

config = Config.shared()

263

264

# Store MCP secrets

265

secrets = config.get(MCP_SECRETS_KEY, {})

266

secrets["my_mcp_server"] = {

267

"api_key": "secret_key_123",

268

"endpoint": "https://mcp.example.com"

269

}

270

config.set(MCP_SECRETS_KEY, secrets)

271

config.save()

272

273

# Retrieve MCP secrets

274

retrieved_secrets = config.get(MCP_SECRETS_KEY, {})

275

server_secret = retrieved_secrets.get("my_mcp_server")

276

print(f"MCP Server endpoint: {server_secret['endpoint']}")

277

```

278

279

### Async Lock Usage

280

281

```python

282

from kiln_ai.utils.lock import shared_async_lock_manager

283

284

# Use shared lock manager

285

async def update_shared_resource(resource_id: str):

286

"""Update resource with lock protection."""

287

lock_id = f"resource_{resource_id}"

288

289

async with shared_async_lock_manager.with_lock(lock_id):

290

# Critical section - only one task can execute this at a time

291

print(f"Updating resource {resource_id}")

292

# Perform update...

293

await asyncio.sleep(1)

294

print(f"Completed update {resource_id}")

295

296

# Multiple concurrent calls will be serialized

297

await asyncio.gather(

298

update_shared_resource("abc"),

299

update_shared_resource("abc"),

300

update_shared_resource("abc")

301

)

302

```

303

304

### Lock for File Operations

305

306

```python

307

from kiln_ai.utils.lock import shared_async_lock_manager

308

import asyncio

309

310

async def safe_file_write(file_path: str, content: str):

311

"""Write to file with lock protection."""

312

lock_id = f"file_{file_path}"

313

314

async with shared_async_lock_manager.with_lock(lock_id):

315

# Only one task can write to this file at a time

316

with open(file_path, "w") as f:

317

f.write(content)

318

await asyncio.sleep(0.1) # Simulate processing

319

320

# Safe concurrent writes

321

await asyncio.gather(

322

safe_file_write("/tmp/data.txt", "content1"),

323

safe_file_write("/tmp/data.txt", "content2"),

324

safe_file_write("/tmp/other.txt", "content3") # Different file, can run in parallel

325

)

326

```

327

328

### String Formatting

329

330

```python

331

from kiln_ai.utils.formatting import snake_case

332

333

# Convert various formats to snake_case

334

test_strings = [

335

"HelloWorld",

336

"camelCase",

337

"PascalCase",

338

"already_snake_case",

339

"SCREAMING_SNAKE_CASE",

340

"Mixed-Format_String"

341

]

342

343

print("String conversions:")

344

for s in test_strings:

345

converted = snake_case(s)

346

print(f" {s} -> {converted}")

347

348

# Use for generating IDs

349

class_name = "MyCustomModel"

350

model_id = snake_case(class_name)

351

print(f"\nModel ID: {model_id}") # my_custom_model

352

```

353

354

### Configuration Validation

355

356

```python

357

from kiln_ai.utils.config import Config

358

359

def validate_config():

360

"""Validate configuration has required values."""

361

config = Config.shared()

362

363

errors = []

364

365

# Check for required providers

366

providers = config.openai_compatible_providers or []

367

if not providers:

368

errors.append("No OpenAI compatible providers configured")

369

370

# Check custom models format

371

custom_models = config.custom_models or []

372

for model in custom_models:

373

if "::" not in model:

374

errors.append(f"Invalid model format: {model} (expected 'provider::model')")

375

376

if errors:

377

print("Configuration errors:")

378

for error in errors:

379

print(f" - {error}")

380

return False

381

else:

382

print("Configuration valid")

383

return True

384

385

# Validate

386

validate_config()

387

```

388

389

### Configuration Backup

390

391

```python

392

from kiln_ai.utils.config import Config

393

import json

394

import shutil

395

from pathlib import Path

396

397

def backup_config():

398

"""Backup configuration to JSON."""

399

config = Config.shared()

400

401

backup_data = {

402

"custom_models": config.custom_models,

403

"openai_compatible_providers": config.openai_compatible_providers

404

}

405

406

backup_path = Path.home() / ".kiln_settings" / "config_backup.json"

407

with open(backup_path, "w") as f:

408

json.dump(backup_data, f, indent=2)

409

410

print(f"Config backed up to {backup_path}")

411

412

def restore_config():

413

"""Restore configuration from JSON backup."""

414

backup_path = Path.home() / ".kiln_settings" / "config_backup.json"

415

416

if not backup_path.exists():

417

print("No backup found")

418

return

419

420

with open(backup_path, "r") as f:

421

backup_data = json.load(f)

422

423

config = Config.shared()

424

config.custom_models = backup_data.get("custom_models", [])

425

config.openai_compatible_providers = backup_data.get("openai_compatible_providers", [])

426

config.save()

427

428

print("Config restored from backup")

429

430

# Backup before changes

431

backup_config()

432

```

433

434

### Per-Task Configuration

435

436

```python

437

from kiln_ai.utils.config import Config

438

from kiln_ai.datamodel import Task

439

440

class TaskConfig:

441

"""Task-specific configuration wrapper."""

442

443

def __init__(self, task: Task):

444

self.task = task

445

self.global_config = Config.shared()

446

447

def get_model_config(self, model_name: str) -> dict:

448

"""Get configuration for specific model."""

449

# Could store task-specific overrides

450

return {

451

"temperature": 0.7,

452

"max_tokens": 1000

453

}

454

455

def get_custom_models(self) -> list:

456

"""Get custom models including global and task-specific."""

457

global_models = self.global_config.custom_models or []

458

# Could add task-specific models

459

return global_models

460

461

# Use with task

462

task = Task.load_from_file("path/to/task.kiln")

463

task_config = TaskConfig(task)

464

models = task_config.get_custom_models()

465

```

466

467

### Configuration Change Listener

468

469

```python

470

from kiln_ai.utils.config import Config

471

472

class ConfigWatcher:

473

"""Watch for configuration changes."""

474

475

def __init__(self):

476

self.config = Config.shared()

477

self.last_models = list(self.config.custom_models or [])

478

479

def check_changes(self) -> dict:

480

"""Check for configuration changes."""

481

self.config.load() # Reload from disk

482

483

current_models = self.config.custom_models or []

484

changes = {}

485

486

# Check for new models

487

new_models = set(current_models) - set(self.last_models)

488

if new_models:

489

changes["added_models"] = list(new_models)

490

491

# Check for removed models

492

removed_models = set(self.last_models) - set(current_models)

493

if removed_models:

494

changes["removed_models"] = list(removed_models)

495

496

self.last_models = list(current_models)

497

return changes

498

499

# Use watcher

500

watcher = ConfigWatcher()

501

502

# Later...

503

changes = watcher.check_changes()

504

if changes:

505

print("Configuration changes detected:")

506

print(changes)

507

```

508

509

### Thread-Safe Configuration Access

510

511

```python

512

from kiln_ai.utils.config import Config

513

from kiln_ai.utils.lock import shared_async_lock_manager

514

import asyncio

515

516

async def safe_config_update(key: str, value):

517

"""Thread-safe configuration update."""

518

async with shared_async_lock_manager.with_lock("config_lock"):

519

config = Config.shared()

520

config.set(key, value)

521

config.save()

522

print(f"Updated {key} = {value}")

523

524

# Safe concurrent updates

525

await asyncio.gather(

526

safe_config_update("setting1", "value1"),

527

safe_config_update("setting2", "value2"),

528

safe_config_update("setting3", "value3")

529

)

530

```

531

532

### Configuration Migration

533

534

```python

535

from kiln_ai.utils.config import Config

536

537

def migrate_config_v1_to_v2():

538

"""Migrate configuration from v1 to v2 format."""

539

config = Config.shared()

540

541

# Old format: list of model strings

542

old_models = config.get("models", [])

543

544

# New format: list of dicts with metadata

545

if old_models and isinstance(old_models[0], str):

546

new_models = []

547

for model_str in old_models:

548

provider, model = model_str.split("::", 1)

549

new_models.append({

550

"provider": provider,

551

"model": model,

552

"enabled": True

553

})

554

555

config.set("models_v2", new_models)

556

config.save()

557

print("Migrated configuration to v2")

558

559

# Run migration

560

migrate_config_v1_to_v2()

561

```

562