Skip to content

Commit d7acdb8

Browse files
committed
feat(llm): add profile-based configuration system
Implement centralized LLM configuration via ~/.workflows/llm-config.yml with provider reuse and hierarchical parameter resolution. - Add LLMConfigLoader for profile management with built-in defaults - Update LLMCallInput to support profile field with backward compatibility - Integrate LLMConfigLoader into ExecutionContext and AppContext - Update schema.json to reflect new profile-based configuration - Document profile-based configuration in README.md - Add layer1 and agent tags to git-analyze-changes workflow Profiles enable "right-sized" LLM selection similar to AWS instance types, supporting both cloud and local providers with inline parameter overrides.
1 parent 4885f54 commit d7acdb8

11 files changed

Lines changed: 815 additions & 64 deletions

File tree

README.md

Lines changed: 69 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -117,45 +117,86 @@ blocks:
117117

118118
### 🤖 LLM Integration
119119

120-
Call LLM APIs directly from workflows with automatic retry logic and JSON schema validation:
120+
Call LLM APIs directly from workflows with automatic retry logic and JSON schema validation.
121+
122+
**Profile-Based Configuration (Recommended):**
123+
124+
Centralized LLM configuration via `~/.workflows/llm-config.yml`:
121125

122126
```yaml
123-
blocks:
124-
- id: extract_data
125-
type: LLMCall
126-
inputs:
127-
provider: openai
128-
model: gpt-4o-mini
129-
api_key: "{{secrets.OPENAI_API_KEY}}"
130-
prompt: "Extract key information from: {{inputs.text}}"
131-
response_schema:
132-
type: object
133-
required: [summary, confidence]
134-
properties:
135-
summary: {type: string}
136-
confidence: {type: number, minimum: 0, maximum: 1}
137-
max_retries: 3
127+
version: "1.0"
128+
129+
providers:
130+
cloud:
131+
type: openai
132+
api_key_secret: "OPENAI_API_KEY"
133+
134+
local:
135+
type: openai
136+
api_url: "http://127.0.0.1:1234/v1/chat/completions"
137+
138+
profiles:
139+
default:
140+
provider: cloud
141+
model: gpt-4o-mini
142+
temperature: 0.7
143+
max_tokens: 4000
144+
145+
local:
146+
provider: local
147+
model: ""
148+
temperature: 0.8
149+
max_tokens: 4000
150+
151+
default_profile: default
138152
```
139153
140-
**Supported Providers:**
154+
Usage:
155+
156+
```yaml
157+
# Profile-based
158+
- id: analyze
159+
type: LLMCall
160+
inputs:
161+
profile: default
162+
prompt: "Analyze: {{inputs.text}}"
163+
164+
# Profile with overrides
165+
- id: summary
166+
type: LLMCall
167+
inputs:
168+
profile: default
169+
temperature: 0.1
170+
prompt: "Summarize: {{inputs.text}}"
171+
```
141172
142-
- **OpenAI** - API access with native Structured Outputs
143-
- **Anthropic** - API access for Claude models
144-
- **Gemini** - API access for Gemini models
145-
- **Ollama** - Local Ollama server API
146-
- **OpenAI-compatible** - LM Studio, vLLM, and other compatible endpoints
173+
**Direct Specification (Backward Compatible):**
174+
175+
```yaml
176+
- id: extract_data
177+
type: LLMCall
178+
inputs:
179+
provider: openai
180+
model: gpt-4o-mini
181+
api_key: "{{secrets.OPENAI_API_KEY}}"
182+
prompt: "Extract: {{inputs.text}}"
183+
response_schema:
184+
type: object
185+
required: [summary, confidence]
186+
properties:
187+
summary: {type: string}
188+
confidence: {type: number, minimum: 0, maximum: 1}
189+
```
190+
191+
**Supported Providers:** OpenAI, Anthropic, Gemini, Ollama, OpenAI-compatible (LM Studio, vLLM)
147192
148193
**Key Features:**
149194
150-
- ✅ **Native schema validation** - Send JSON Schema to API for guaranteed structure
151-
- ✅ **Automatic retry** - Exponential backoff with validation feedback loop
195+
- ✅ **Profile-based config** - Centralized provider/model management
196+
- ✅ **Schema validation** - Guaranteed JSON structure with automatic retry
152197
- ✅ **Token tracking** - Monitor usage and costs
153198
- ✅ **Secrets integration** - Secure API key management
154199
155-
**For Local CLI Tools:**
156-
157-
To use local Claude CLI or Gemini CLI instead of APIs, use the `llm-process` workflow template which wraps CLI commands.
158-
159200
## What Can You Do With It?
160201
161202
### Built-in Workflows

schema.json

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -615,23 +615,47 @@
615615
"additionalProperties": false,
616616
"description": "Inputs for LLMCall block",
617617
"properties": {
618+
"profile": {
619+
"anyOf": [
620+
{
621+
"type": "string"
622+
},
623+
{
624+
"type": "null"
625+
}
626+
],
627+
"default": null,
628+
"description": "Profile name from ~/.workflows/llm-config.yml (e.g., 'cloud', 'local', 'default'). If specified, provider/model are loaded from config. Mutually exclusive with direct provider/model specification.",
629+
"title": "Profile"
630+
},
618631
"provider": {
619632
"anyOf": [
620633
{
621634
"$ref": "#/$defs/LLMProvider"
622635
},
623636
{
624637
"type": "string"
638+
},
639+
{
640+
"type": "null"
625641
}
626642
],
627-
"default": "openai",
628-
"description": "LLM provider (enum or interpolation string)",
643+
"default": null,
644+
"description": "LLM provider (enum or interpolation string). Required if profile not specified. Ignored if profile specified.",
629645
"title": "Provider"
630646
},
631647
"model": {
632-
"description": "Model name (e.g., gpt-4o, claude-3-5-sonnet-20241022, gemini-2.0-flash-exp)",
633-
"title": "Model",
634-
"type": "string"
648+
"anyOf": [
649+
{
650+
"type": "string"
651+
},
652+
{
653+
"type": "null"
654+
}
655+
],
656+
"default": null,
657+
"description": "Model name (e.g., gpt-4o, claude-3-5-sonnet-20241022, gemini-2.0-flash-exp). Required if profile not specified. Can override profile model if both specified.",
658+
"title": "Model"
635659
},
636660
"prompt": {
637661
"description": "User prompt to send to the LLM",
@@ -773,7 +797,6 @@
773797
}
774798
},
775799
"required": [
776-
"model",
777800
"prompt"
778801
],
779802
"title": "LLMCallInput",
@@ -1357,25 +1380,49 @@
13571380
},
13581381
"LLMCallInput": {
13591382
"additionalProperties": false,
1360-
"description": "Input model for LLMCall executor.\n\nAll inputs are pre-resolved by VariableResolver:\n- {{secrets.OPENAI_API_KEY}} \u2192 actual key value\n- {{inputs.prompt}} \u2192 actual prompt text\n- {{blocks.previous.outputs.data}} \u2192 actual data",
1383+
"description": "Input model for LLMCall executor.\n\nAll inputs are pre-resolved by VariableResolver:\n- {{secrets.OPENAI_API_KEY}} \u2192 actual key value\n- {{inputs.prompt}} \u2192 actual prompt text\n- {{blocks.previous.outputs.data}} \u2192 actual data\n\nConfiguration Modes:\n1. Profile-based (NEW): Specify profile name, optionally override parameters\n2. Direct specification (backward compatible): Specify provider + model directly\n\nExamples:\n # Profile-based (recommended)\n profile: quick\n prompt: \"...\"\n\n # Profile with overrides\n profile: standard\n temperature: 1.0\n max_tokens: 8000\n prompt: \"...\"\n\n # Direct specification (backward compatible)\n provider: openai\n model: gpt-4o\n api_key: \"{{secrets.OPENAI_API_KEY}}\"\n prompt: \"...\"",
13611384
"properties": {
1385+
"profile": {
1386+
"anyOf": [
1387+
{
1388+
"type": "string"
1389+
},
1390+
{
1391+
"type": "null"
1392+
}
1393+
],
1394+
"default": null,
1395+
"description": "Profile name from ~/.workflows/llm-config.yml (e.g., 'cloud', 'local', 'default'). If specified, provider/model are loaded from config. Mutually exclusive with direct provider/model specification.",
1396+
"title": "Profile"
1397+
},
13621398
"provider": {
13631399
"anyOf": [
13641400
{
13651401
"$ref": "#/$defs/LLMProvider"
13661402
},
13671403
{
13681404
"type": "string"
1405+
},
1406+
{
1407+
"type": "null"
13691408
}
13701409
],
1371-
"default": "openai",
1372-
"description": "LLM provider (enum or interpolation string)",
1410+
"default": null,
1411+
"description": "LLM provider (enum or interpolation string). Required if profile not specified. Ignored if profile specified.",
13731412
"title": "Provider"
13741413
},
13751414
"model": {
1376-
"description": "Model name (e.g., gpt-4o, claude-3-5-sonnet-20241022, gemini-2.0-flash-exp)",
1377-
"title": "Model",
1378-
"type": "string"
1415+
"anyOf": [
1416+
{
1417+
"type": "string"
1418+
},
1419+
{
1420+
"type": "null"
1421+
}
1422+
],
1423+
"default": null,
1424+
"description": "Model name (e.g., gpt-4o, claude-3-5-sonnet-20241022, gemini-2.0-flash-exp). Required if profile not specified. Can override profile model if both specified.",
1425+
"title": "Model"
13791426
},
13801427
"prompt": {
13811428
"description": "User prompt to send to the LLM",
@@ -1517,7 +1564,6 @@
15171564
}
15181565
},
15191566
"required": [
1520-
"model",
15211567
"prompt"
15221568
],
15231569
"title": "LLMCallInput",

src/src/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
print('hello world')

src/src/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Utility functions

src/workflows_mcp/context.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .engine import ExecutionContext, WorkflowRegistry
1313
from .engine.checkpoint_store import CheckpointStore
1414
from .engine.executor_base import ExecutorRegistry
15+
from .engine.llm_config import LLMConfigLoader
1516

1617

1718
@dataclass
@@ -27,6 +28,7 @@ class AppContext:
2728
registry: WorkflowRegistry
2829
executor_registry: ExecutorRegistry
2930
checkpoint_store: CheckpointStore
31+
llm_config_loader: LLMConfigLoader
3032
max_recursion_depth: int = 50 # Default recursion depth limit
3133

3234
def create_execution_context(self) -> ExecutionContext:
@@ -39,6 +41,7 @@ def create_execution_context(self) -> ExecutionContext:
3941
workflow_registry=self.registry,
4042
executor_registry=self.executor_registry,
4143
checkpoint_store=self.checkpoint_store,
44+
llm_config_loader=self.llm_config_loader,
4245
parent=None,
4346
workflow_stack=[],
4447
max_recursion_depth=self.max_recursion_depth,

src/workflows_mcp/engine/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
executors_file, # File operation executors
3838
executors_http, # HTTP/REST API executor
3939
executors_interactive, # Interactive executors
40+
executors_llm, # LLM call executor
4041
executors_state, # JSON state executors
4142
executors_workflow, # Workflow executor (ADR-008)
4243
)

src/workflows_mcp/engine/execution_context.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from .checkpoint_store import CheckpointStore
2222
from .execution import Execution
2323
from .executor_base import ExecutorRegistry
24+
from .llm_config import LLMConfigLoader
2425
from .registry import WorkflowRegistry
2526
from .schema import WorkflowSchema
2627

@@ -48,6 +49,7 @@ def __init__(
4849
workflow_registry: WorkflowRegistry,
4950
executor_registry: ExecutorRegistry,
5051
checkpoint_store: CheckpointStore,
52+
llm_config_loader: LLMConfigLoader,
5153
parent: Execution | None = None,
5254
workflow_stack: list[str] | None = None,
5355
max_recursion_depth: int = 50,
@@ -59,13 +61,15 @@ def __init__(
5961
workflow_registry: Registry of workflow definitions
6062
executor_registry: Registry of block executors
6163
checkpoint_store: Store for checkpoints (pause/resume)
64+
llm_config_loader: Loader for LLM profile configuration
6265
parent: Parent execution (for nested workflows)
6366
workflow_stack: Workflow execution stack (depth tracking)
6467
max_recursion_depth: Maximum allowed recursion depth (default: 50)
6568
"""
6669
self.workflow_registry = workflow_registry
6770
self.executor_registry = executor_registry
6871
self.checkpoint_store = checkpoint_store
72+
self.llm_config_loader = llm_config_loader
6973
self.parent = parent
7074
self.workflow_stack = workflow_stack or []
7175
self.max_recursion_depth = max_recursion_depth
@@ -107,6 +111,7 @@ def create_child_context(
107111
workflow_registry=self.workflow_registry,
108112
executor_registry=self.executor_registry,
109113
checkpoint_store=self.checkpoint_store,
114+
llm_config_loader=self.llm_config_loader,
110115
parent=parent_execution,
111116
workflow_stack=self.workflow_stack + [workflow_name],
112117
max_recursion_depth=self.max_recursion_depth,

0 commit comments

Comments
 (0)