or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdembeddings.mdindex.mdmodels-and-conversations.mdplugins.mdtemplates.mdtools-and-toolboxes.md

plugins.mddocs/

0

# Plugins

1

2

Extensible plugin architecture with hook specifications for registering models, tools, templates, and commands. This module enables third-party extensions and custom integrations through a comprehensive plugin system built on Pluggy.

3

4

## Capabilities

5

6

### Plugin Discovery and Management

7

8

Functions to discover and work with plugins in the LLM ecosystem.

9

10

```python { .api }

11

def get_plugins(all: bool = False) -> List[dict]:

12

"""

13

Get list of registered plugins with metadata.

14

15

Args:

16

all: If True, include default plugins. If False, only show third-party plugins.

17

18

Returns:

19

List of plugin dictionaries containing:

20

- name: Plugin name

21

- hooks: List of hook names implemented

22

- version: Plugin version (if available)

23

"""

24

25

def load_plugins():

26

"""Load all registered plugins from entry points."""

27

```

28

29

### Plugin Manager

30

31

The global plugin manager instance that coordinates all plugin operations.

32

33

```python { .api }

34

pm: PluginManager

35

"""

36

Global plugin manager instance using Pluggy framework.

37

38

Provides hook calling and plugin management functionality.

39

Available hook callers:

40

- pm.hook.register_models

41

- pm.hook.register_embedding_models

42

- pm.hook.register_tools

43

- pm.hook.register_template_loaders

44

- pm.hook.register_fragment_loaders

45

- pm.hook.register_commands

46

"""

47

```

48

49

### Hook Implementation Decorator

50

51

Decorator for implementing plugin hooks.

52

53

```python { .api }

54

@hookimpl

55

def plugin_function(*args, **kwargs):

56

"""

57

Decorator for implementing plugin hook functions.

58

59

Used to mark functions as implementations of specific hooks.

60

The function name should match the hook specification.

61

"""

62

```

63

64

### Hook Specifications

65

66

The plugin system defines several hook specifications that plugins can implement.

67

68

```python { .api }

69

def register_models(register):

70

"""

71

Hook for registering LLM models.

72

73

Args:

74

register: Function to call with (model, async_model, aliases)

75

76

Example:

77

@hookimpl

78

def register_models(register):

79

register(MyModel("my-model"), aliases=["alias1", "alias2"])

80

"""

81

82

def register_embedding_models(register):

83

"""

84

Hook for registering embedding models.

85

86

Args:

87

register: Function to call with (model, aliases)

88

89

Example:

90

@hookimpl

91

def register_embedding_models(register):

92

register(MyEmbeddingModel("my-embeddings"), aliases=["embed"])

93

"""

94

95

def register_tools(register):

96

"""

97

Hook for registering tools and toolboxes.

98

99

Args:

100

register: Function to call with (tool_or_function, name)

101

102

Example:

103

@hookimpl

104

def register_tools(register):

105

register(my_function, name="my_tool")

106

register(MyToolbox, name="my_toolbox")

107

"""

108

109

def register_template_loaders(register):

110

"""

111

Hook for registering template loaders.

112

113

Args:

114

register: Function to call with (prefix, loader_function)

115

116

Example:

117

@hookimpl

118

def register_template_loaders(register):

119

register("yaml", yaml_template_loader)

120

"""

121

122

def register_fragment_loaders(register):

123

"""

124

Hook for registering fragment loaders.

125

126

Args:

127

register: Function to call with (prefix, loader_function)

128

129

Example:

130

@hookimpl

131

def register_fragment_loaders(register):

132

register("file", file_fragment_loader)

133

"""

134

135

def register_commands(cli):

136

"""

137

Hook for registering CLI commands.

138

139

Args:

140

cli: Click CLI group to add commands to

141

142

Example:

143

@hookimpl

144

def register_commands(cli):

145

@cli.command()

146

def my_command():

147

click.echo("Hello from plugin!")

148

"""

149

```

150

151

## Usage Examples

152

153

### Basic Plugin Implementation

154

155

```python

156

import llm

157

158

# Create a simple plugin file (e.g., my_plugin.py)

159

@llm.hookimpl

160

def register_models(register):

161

"""Register a custom model."""

162

163

class EchoModel(llm.Model):

164

"""A model that echoes back the input."""

165

166

model_id = "echo"

167

168

def prompt(self, prompt, **kwargs):

169

# Simple echo implementation

170

return EchoResponse(f"Echo: {prompt}")

171

172

class EchoResponse(llm.Response):

173

def __init__(self, text):

174

self._text = text

175

176

def text(self):

177

return self._text

178

179

def __iter__(self):

180

yield self._text

181

182

# Register the model with aliases

183

register(EchoModel(), aliases=["echo", "test"])

184

185

# Plugin is automatically discovered and loaded

186

```

187

188

### Tool Plugin Example

189

190

```python

191

import llm

192

import requests

193

194

@llm.hookimpl

195

def register_tools(register):

196

"""Register HTTP tools."""

197

198

def http_get(url: str) -> str:

199

"""Make HTTP GET request and return response text."""

200

try:

201

response = requests.get(url, timeout=10)

202

response.raise_for_status()

203

return response.text[:1000] # Truncate for safety

204

except requests.RequestException as e:

205

raise llm.CancelToolCall(f"HTTP request failed: {e}")

206

207

def http_post(url: str, data: str) -> str:

208

"""Make HTTP POST request with data."""

209

try:

210

response = requests.post(url, data=data, timeout=10)

211

response.raise_for_status()

212

return f"POST successful: {response.status_code}"

213

except requests.RequestException as e:

214

raise llm.CancelToolCall(f"HTTP POST failed: {e}")

215

216

# Register individual tools

217

register(http_get, name="http_get")

218

register(http_post, name="http_post")

219

220

# Register a toolbox

221

class HttpToolbox(llm.Toolbox):

222

"""Collection of HTTP tools."""

223

224

def tools(self):

225

return [

226

llm.Tool.function(http_get),

227

llm.Tool.function(http_post),

228

llm.Tool.function(self.http_head)

229

]

230

231

def http_head(self, url: str) -> dict:

232

"""Make HTTP HEAD request and return headers."""

233

try:

234

response = requests.head(url, timeout=10)

235

response.raise_for_status()

236

return dict(response.headers)

237

except requests.RequestException as e:

238

raise llm.CancelToolCall(f"HTTP HEAD failed: {e}")

239

240

register(HttpToolbox, name="http")

241

242

# Use the registered tools

243

tools = llm.get_tools()

244

http_tools = [t for name, t in tools.items() if name.startswith("http")]

245

```

246

247

### Template Loader Plugin

248

249

```python

250

import llm

251

import yaml

252

import json

253

254

@llm.hookimpl

255

def register_template_loaders(register):

256

"""Register custom template loaders."""

257

258

def yaml_loader(spec: str) -> llm.Template:

259

"""Load template from YAML specification."""

260

try:

261

config = yaml.safe_load(spec)

262

return llm.Template(

263

name=config['name'],

264

prompt=config.get('prompt'),

265

system=config.get('system'),

266

model=config.get('model'),

267

defaults=config.get('defaults', {}),

268

options=config.get('options', {})

269

)

270

except Exception as e:

271

raise ValueError(f"Invalid YAML template: {e}")

272

273

def json_loader(spec: str) -> llm.Template:

274

"""Load template from JSON specification."""

275

try:

276

config = json.loads(spec)

277

return llm.Template(**config)

278

except Exception as e:

279

raise ValueError(f"Invalid JSON template: {e}")

280

281

def file_loader(file_path: str) -> llm.Template:

282

"""Load template from file."""

283

import os

284

if not os.path.exists(file_path):

285

raise FileNotFoundError(f"Template file not found: {file_path}")

286

287

with open(file_path) as f:

288

content = f.read()

289

290

# Simple template format

291

lines = content.strip().split('\n')

292

name = lines[0].replace('# ', '')

293

prompt = '\n'.join(lines[1:])

294

295

return llm.Template(name=name, prompt=prompt)

296

297

register("yaml", yaml_loader)

298

register("json", json_loader)

299

register("file", file_loader)

300

301

# Template loaders are now available

302

loaders = llm.get_template_loaders()

303

print(f"Available loaders: {list(loaders.keys())}")

304

```

305

306

### Fragment Loader Plugin

307

308

```python

309

import llm

310

import os

311

312

@llm.hookimpl

313

def register_fragment_loaders(register):

314

"""Register fragment loaders for modular content."""

315

316

def file_fragment_loader(file_path: str) -> llm.Fragment:

317

"""Load fragment from file."""

318

if not os.path.exists(file_path):

319

raise FileNotFoundError(f"Fragment file not found: {file_path}")

320

321

with open(file_path) as f:

322

content = f.read()

323

324

return llm.Fragment(content, source=f"file:{file_path}")

325

326

def url_fragment_loader(url: str) -> llm.Fragment:

327

"""Load fragment from URL."""

328

import requests

329

try:

330

response = requests.get(url, timeout=10)

331

response.raise_for_status()

332

return llm.Fragment(response.text, source=f"url:{url}")

333

except requests.RequestException as e:

334

raise ValueError(f"Could not load fragment from {url}: {e}")

335

336

def env_fragment_loader(env_var: str) -> llm.Fragment:

337

"""Load fragment from environment variable."""

338

value = os.getenv(env_var)

339

if value is None:

340

raise ValueError(f"Environment variable not set: {env_var}")

341

342

return llm.Fragment(value, source=f"env:{env_var}")

343

344

register("file", file_fragment_loader)

345

register("url", url_fragment_loader)

346

register("env", env_fragment_loader)

347

348

# Fragment loaders enable modular content

349

fragment_loaders = llm.get_fragment_loaders()

350

print(f"Fragment loaders: {list(fragment_loaders.keys())}")

351

```

352

353

### CLI Command Plugin

354

355

```python

356

import llm

357

import click

358

359

@llm.hookimpl

360

def register_commands(cli):

361

"""Register custom CLI commands."""

362

363

@cli.group()

364

def analyze():

365

"""Text analysis commands."""

366

pass

367

368

@analyze.command()

369

@click.argument("text")

370

@click.option("--model", "-m", default="gpt-3.5-turbo")

371

def sentiment(text, model):

372

"""Analyze sentiment of text."""

373

model_obj = llm.get_model(model)

374

response = model_obj.prompt(f"Analyze the sentiment of this text: {text}")

375

click.echo(response.text())

376

377

@analyze.command()

378

@click.argument("text")

379

@click.option("--model", "-m", default="gpt-3.5-turbo")

380

def summarize(text, model):

381

"""Summarize text."""

382

model_obj = llm.get_model(model)

383

response = model_obj.prompt(f"Summarize this text in one sentence: {text}")

384

click.echo(response.text())

385

386

@cli.command()

387

@click.option("--format", type=click.Choice(["json", "yaml", "table"]), default="table")

388

def plugin_info(format):

389

"""Show information about loaded plugins."""

390

plugins = llm.get_plugins(all=True)

391

392

if format == "json":

393

import json

394

click.echo(json.dumps(plugins, indent=2))

395

elif format == "yaml":

396

import yaml

397

click.echo(yaml.dump(plugins))

398

else:

399

# Table format

400

for plugin in plugins:

401

click.echo(f"Plugin: {plugin['name']}")

402

click.echo(f" Hooks: {', '.join(plugin['hooks'])}")

403

if 'version' in plugin:

404

click.echo(f" Version: {plugin['version']}")

405

click.echo()

406

407

# Commands are now available: llm analyze sentiment "I love this!"

408

```

409

410

### Complete Plugin Example

411

412

```python

413

# File: llm_weather_plugin.py

414

import llm

415

import requests

416

import json

417

418

class WeatherModel(llm.KeyModel):

419

"""Model that provides weather information."""

420

421

model_id = "weather"

422

needs_key = "weather"

423

key_env_var = "WEATHER_API_KEY"

424

425

def prompt(self, prompt, **kwargs):

426

# Extract location from prompt (simplified)

427

location = prompt.text() if hasattr(prompt, 'text') else str(prompt)

428

429

# Get weather data

430

api_key = self.get_key()

431

url = f"http://api.openweathermap.org/data/2.5/weather"

432

params = {"q": location, "appid": api_key, "units": "metric"}

433

434

response = requests.get(url, params=params)

435

weather_data = response.json()

436

437

if response.status_code == 200:

438

temp = weather_data["main"]["temp"]

439

desc = weather_data["weather"][0]["description"]

440

result = f"Weather in {location}: {temp}°C, {desc}"

441

else:

442

result = f"Could not get weather for {location}"

443

444

return WeatherResponse(result)

445

446

class WeatherResponse(llm.Response):

447

def __init__(self, text):

448

self._text = text

449

450

def text(self):

451

return self._text

452

453

def __iter__(self):

454

yield self._text

455

456

@llm.hookimpl

457

def register_models(register):

458

"""Register weather model."""

459

register(WeatherModel(), aliases=["weather", "forecast"])

460

461

@llm.hookimpl

462

def register_tools(register):

463

"""Register weather tools."""

464

465

def current_weather(location: str) -> str:

466

"""Get current weather for a location."""

467

# Implementation would use weather API

468

return f"Current weather in {location}: 22°C, sunny"

469

470

def weather_forecast(location: str, days: int = 5) -> str:

471

"""Get weather forecast for a location."""

472

return f"{days}-day forecast for {location}: Mostly sunny"

473

474

register(current_weather, name="weather")

475

register(weather_forecast, name="forecast")

476

477

@llm.hookimpl

478

def register_commands(cli):

479

"""Register weather CLI commands."""

480

481

@cli.command()

482

@click.argument("location")

483

def weather(location):

484

"""Get weather for a location."""

485

model = llm.get_model("weather")

486

response = model.prompt(location)

487

click.echo(response.text())

488

489

# Entry point in setup.py or pyproject.toml:

490

# [project.entry-points."llm"]

491

# weather = "llm_weather_plugin"

492

```

493

494

### Plugin Discovery and Inspection

495

496

```python

497

import llm

498

499

# Load all plugins

500

llm.load_plugins()

501

502

# Get plugin information

503

plugins = llm.get_plugins(all=True)

504

print("All plugins:")

505

for plugin in plugins:

506

print(f"- {plugin['name']}: {plugin['hooks']}")

507

if 'version' in plugin:

508

print(f" Version: {plugin['version']}")

509

510

# Get only third-party plugins

511

third_party = llm.get_plugins(all=False)

512

print(f"\nThird-party plugins: {len(third_party)}")

513

514

# Inspect available extensions

515

tools = llm.get_tools()

516

print(f"Available tools: {len(tools)}")

517

518

template_loaders = llm.get_template_loaders()

519

print(f"Template loaders: {list(template_loaders.keys())}")

520

521

fragment_loaders = llm.get_fragment_loaders()

522

print(f"Fragment loaders: {list(fragment_loaders.keys())}")

523

524

# Direct plugin manager access

525

plugin_names = [llm.pm.get_name(plugin) for plugin in llm.pm.get_plugins()]

526

print(f"Plugin manager has: {plugin_names}")

527

```

528

529

### Plugin Development Best Practices

530

531

```python

532

import llm

533

534

# Example of well-structured plugin

535

class MyPlugin:

536

"""Example plugin demonstrating best practices."""

537

538

def __init__(self):

539

self.initialized = False

540

541

def ensure_initialized(self):

542

if not self.initialized:

543

# Lazy initialization

544

self.setup_resources()

545

self.initialized = True

546

547

def setup_resources(self):

548

# Initialize any required resources

549

pass

550

551

# Global plugin instance

552

plugin_instance = MyPlugin()

553

554

@llm.hookimpl

555

def register_models(register):

556

"""Register models with proper error handling."""

557

try:

558

plugin_instance.ensure_initialized()

559

# Register models

560

pass

561

except Exception as e:

562

# Log error but don't crash plugin loading

563

print(f"Failed to register models: {e}")

564

565

@llm.hookimpl

566

def register_tools(register):

567

"""Register tools with validation."""

568

plugin_instance.ensure_initialized()

569

570

def validated_tool(input_data: str) -> str:

571

"""Tool with input validation."""

572

if not input_data.strip():

573

raise llm.CancelToolCall("Input cannot be empty")

574

575

# Process input

576

return f"Processed: {input_data}"

577

578

register(validated_tool, name="validated_tool")

579

580

# Plugin metadata (for entry points)

581

__version__ = "1.0.0"

582

__author__ = "Plugin Developer"

583

__description__ = "Example plugin for LLM"

584

```

585

586

This comprehensive plugin system enables extensive customization and integration of the LLM package with external services, custom models, specialized tools, and domain-specific functionality while maintaining a clean and consistent API.