cognitive-modules 0.1.1__tar.gz → 0.3.0__tar.gz

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.
Files changed (28) hide show
  1. {cognitive_modules-0.1.1/src/cognitive_modules.egg-info → cognitive_modules-0.3.0}/PKG-INFO +131 -2
  2. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/README.md +130 -1
  3. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/pyproject.toml +1 -1
  4. cognitive_modules-0.3.0/src/cognitive/loader.py +258 -0
  5. cognitive_modules-0.3.0/src/cognitive/runner.py +276 -0
  6. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/src/cognitive/templates.py +1 -1
  7. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0/src/cognitive_modules.egg-info}/PKG-INFO +131 -2
  8. cognitive_modules-0.1.1/src/cognitive/loader.py +0 -133
  9. cognitive_modules-0.1.1/src/cognitive/runner.py +0 -140
  10. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/LICENSE +0 -0
  11. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/setup.cfg +0 -0
  12. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/src/cognitive/__init__.py +0 -0
  13. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/src/cognitive/cli.py +0 -0
  14. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/src/cognitive/providers/__init__.py +0 -0
  15. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/src/cognitive/registry.py +0 -0
  16. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/src/cognitive/subagent.py +0 -0
  17. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/src/cognitive/validator.py +0 -0
  18. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/src/cognitive_modules.egg-info/SOURCES.txt +0 -0
  19. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/src/cognitive_modules.egg-info/dependency_links.txt +0 -0
  20. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/src/cognitive_modules.egg-info/entry_points.txt +0 -0
  21. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/src/cognitive_modules.egg-info/requires.txt +0 -0
  22. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/src/cognitive_modules.egg-info/top_level.txt +0 -0
  23. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/tests/test_cli.py +0 -0
  24. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/tests/test_loader.py +0 -0
  25. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/tests/test_registry.py +0 -0
  26. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/tests/test_runner.py +0 -0
  27. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/tests/test_subagent.py +0 -0
  28. {cognitive_modules-0.1.1 → cognitive_modules-0.3.0}/tests/test_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cognitive-modules
3
- Version: 0.1.1
3
+ Version: 0.3.0
4
4
  Summary: Structured LLM task runner with schema validation, confidence scoring, and subagent orchestration
5
5
  Author: ziel-io
6
6
  License: MIT
@@ -151,6 +151,7 @@ cog doctor
151
151
  | 模块 | 功能 | 示例 |
152
152
  |------|------|------|
153
153
  | `code-reviewer` | 代码审查 | `cog run code-reviewer --args "你的代码"` |
154
+ | `code-simplifier` | 代码简化 | `cog run code-simplifier --args "复杂代码"` |
154
155
  | `task-prioritizer` | 任务优先级排序 | `cog run task-prioritizer --args "任务1,任务2"` |
155
156
  | `api-designer` | REST API 设计 | `cog run api-designer --args "订单系统"` |
156
157
  | `ui-spec-generator` | UI 规范生成 | `cog run ui-spec-generator --args "电商首页"` |
@@ -241,6 +242,134 @@ export LLM_PROVIDER=ollama
241
242
  cog doctor
242
243
  ```
243
244
 
245
+ ## 创建新模块(完整流程)
246
+
247
+ 以 `code-simplifier` 为例:
248
+
249
+ ### Step 1: 创建目录结构
250
+
251
+ ```bash
252
+ mkdir -p cognitive/modules/code-simplifier
253
+ ```
254
+
255
+ ### Step 2: 编写 MODULE.md
256
+
257
+ ```bash
258
+ cat > cognitive/modules/code-simplifier/MODULE.md << 'EOF'
259
+ ---
260
+ name: code-simplifier
261
+ version: 1.0.0
262
+ responsibility: Simplify complex code while preserving functionality
263
+
264
+ excludes:
265
+ - Changing the code's behavior
266
+ - Adding new features
267
+ - Removing functionality
268
+
269
+ constraints:
270
+ no_network: true
271
+ no_side_effects: true
272
+ require_confidence: true
273
+ require_rationale: true
274
+ ---
275
+
276
+ # Code Simplifier Module
277
+
278
+ You are an expert at refactoring and simplifying code.
279
+
280
+ ## Input
281
+
282
+ Code to simplify: $ARGUMENTS
283
+
284
+ ## Simplification Strategies
285
+
286
+ 1. **Remove redundancy** - Eliminate duplicate code
287
+ 2. **Improve naming** - Use clear, descriptive names
288
+ 3. **Reduce nesting** - Flatten deep conditionals
289
+ 4. **Simplify logic** - Use built-in functions
290
+
291
+ ## Output Requirements
292
+
293
+ Return JSON containing:
294
+ - `simplified_code`: The simplified version
295
+ - `changes`: List of changes made
296
+ - `summary`: Brief description
297
+ - `rationale`: Explanation of decisions
298
+ - `confidence`: Confidence score [0-1]
299
+ EOF
300
+ ```
301
+
302
+ ### Step 3: 编写 schema.json
303
+
304
+ ```bash
305
+ cat > cognitive/modules/code-simplifier/schema.json << 'EOF'
306
+ {
307
+ "$schema": "https://ziel-io.github.io/cognitive-modules/schema/v1.json",
308
+ "input": {
309
+ "type": "object",
310
+ "properties": {
311
+ "code": { "type": "string" },
312
+ "language": { "type": "string" },
313
+ "$ARGUMENTS": { "type": "string" }
314
+ }
315
+ },
316
+ "output": {
317
+ "type": "object",
318
+ "required": ["simplified_code", "changes", "summary", "rationale", "confidence"],
319
+ "properties": {
320
+ "simplified_code": { "type": "string" },
321
+ "changes": {
322
+ "type": "array",
323
+ "items": {
324
+ "type": "object",
325
+ "properties": {
326
+ "type": { "type": "string" },
327
+ "description": { "type": "string" }
328
+ }
329
+ }
330
+ },
331
+ "summary": { "type": "string" },
332
+ "rationale": { "type": "string" },
333
+ "confidence": { "type": "number", "minimum": 0, "maximum": 1 }
334
+ }
335
+ }
336
+ }
337
+ EOF
338
+ ```
339
+
340
+ ### Step 4: 验证模块
341
+
342
+ ```bash
343
+ cog validate code-simplifier
344
+ cog list # 确认模块出现在列表中
345
+ ```
346
+
347
+ ### Step 5: 测试运行
348
+
349
+ ```bash
350
+ cog run code-simplifier --args "def calc(x): if x > 0: if x < 10: return x * 2 else: return x else: return 0" --pretty
351
+ ```
352
+
353
+ ### Step 6: 添加示例(可选)
354
+
355
+ ```bash
356
+ mkdir -p cognitive/modules/code-simplifier/examples
357
+ # 添加 input.json 和 output.json 作为测试用例
358
+ ```
359
+
360
+ ### 模块设计要点
361
+
362
+ | 要素 | 必须 | 说明 |
363
+ |------|------|------|
364
+ | `name` | ✅ | 唯一标识符,kebab-case |
365
+ | `version` | ✅ | 语义化版本 |
366
+ | `responsibility` | ✅ | 一句话描述职责 |
367
+ | `excludes` | ✅ | 明确列出不做的事 |
368
+ | `$ARGUMENTS` | ✅ | 支持命令行参数 |
369
+ | `confidence` | ✅ | 输出必须包含 0-1 置信度 |
370
+ | `rationale` | ✅ | 输出必须包含推理过程 |
371
+ | `schema.json` | ✅ | 定义输入输出契约 |
372
+
244
373
  ## 开发
245
374
 
246
375
  ```bash
@@ -254,7 +383,7 @@ pip install -e ".[dev]"
254
383
  # 运行测试
255
384
  pytest tests/ -v
256
385
 
257
- # 创建新模块
386
+ # 创建新模块(使用模板)
258
387
  cog init my-module -d "模块描述"
259
388
  cog validate my-module
260
389
  ```
@@ -99,6 +99,7 @@ cog doctor
99
99
  | 模块 | 功能 | 示例 |
100
100
  |------|------|------|
101
101
  | `code-reviewer` | 代码审查 | `cog run code-reviewer --args "你的代码"` |
102
+ | `code-simplifier` | 代码简化 | `cog run code-simplifier --args "复杂代码"` |
102
103
  | `task-prioritizer` | 任务优先级排序 | `cog run task-prioritizer --args "任务1,任务2"` |
103
104
  | `api-designer` | REST API 设计 | `cog run api-designer --args "订单系统"` |
104
105
  | `ui-spec-generator` | UI 规范生成 | `cog run ui-spec-generator --args "电商首页"` |
@@ -189,6 +190,134 @@ export LLM_PROVIDER=ollama
189
190
  cog doctor
190
191
  ```
191
192
 
193
+ ## 创建新模块(完整流程)
194
+
195
+ 以 `code-simplifier` 为例:
196
+
197
+ ### Step 1: 创建目录结构
198
+
199
+ ```bash
200
+ mkdir -p cognitive/modules/code-simplifier
201
+ ```
202
+
203
+ ### Step 2: 编写 MODULE.md
204
+
205
+ ```bash
206
+ cat > cognitive/modules/code-simplifier/MODULE.md << 'EOF'
207
+ ---
208
+ name: code-simplifier
209
+ version: 1.0.0
210
+ responsibility: Simplify complex code while preserving functionality
211
+
212
+ excludes:
213
+ - Changing the code's behavior
214
+ - Adding new features
215
+ - Removing functionality
216
+
217
+ constraints:
218
+ no_network: true
219
+ no_side_effects: true
220
+ require_confidence: true
221
+ require_rationale: true
222
+ ---
223
+
224
+ # Code Simplifier Module
225
+
226
+ You are an expert at refactoring and simplifying code.
227
+
228
+ ## Input
229
+
230
+ Code to simplify: $ARGUMENTS
231
+
232
+ ## Simplification Strategies
233
+
234
+ 1. **Remove redundancy** - Eliminate duplicate code
235
+ 2. **Improve naming** - Use clear, descriptive names
236
+ 3. **Reduce nesting** - Flatten deep conditionals
237
+ 4. **Simplify logic** - Use built-in functions
238
+
239
+ ## Output Requirements
240
+
241
+ Return JSON containing:
242
+ - `simplified_code`: The simplified version
243
+ - `changes`: List of changes made
244
+ - `summary`: Brief description
245
+ - `rationale`: Explanation of decisions
246
+ - `confidence`: Confidence score [0-1]
247
+ EOF
248
+ ```
249
+
250
+ ### Step 3: 编写 schema.json
251
+
252
+ ```bash
253
+ cat > cognitive/modules/code-simplifier/schema.json << 'EOF'
254
+ {
255
+ "$schema": "https://ziel-io.github.io/cognitive-modules/schema/v1.json",
256
+ "input": {
257
+ "type": "object",
258
+ "properties": {
259
+ "code": { "type": "string" },
260
+ "language": { "type": "string" },
261
+ "$ARGUMENTS": { "type": "string" }
262
+ }
263
+ },
264
+ "output": {
265
+ "type": "object",
266
+ "required": ["simplified_code", "changes", "summary", "rationale", "confidence"],
267
+ "properties": {
268
+ "simplified_code": { "type": "string" },
269
+ "changes": {
270
+ "type": "array",
271
+ "items": {
272
+ "type": "object",
273
+ "properties": {
274
+ "type": { "type": "string" },
275
+ "description": { "type": "string" }
276
+ }
277
+ }
278
+ },
279
+ "summary": { "type": "string" },
280
+ "rationale": { "type": "string" },
281
+ "confidence": { "type": "number", "minimum": 0, "maximum": 1 }
282
+ }
283
+ }
284
+ }
285
+ EOF
286
+ ```
287
+
288
+ ### Step 4: 验证模块
289
+
290
+ ```bash
291
+ cog validate code-simplifier
292
+ cog list # 确认模块出现在列表中
293
+ ```
294
+
295
+ ### Step 5: 测试运行
296
+
297
+ ```bash
298
+ cog run code-simplifier --args "def calc(x): if x > 0: if x < 10: return x * 2 else: return x else: return 0" --pretty
299
+ ```
300
+
301
+ ### Step 6: 添加示例(可选)
302
+
303
+ ```bash
304
+ mkdir -p cognitive/modules/code-simplifier/examples
305
+ # 添加 input.json 和 output.json 作为测试用例
306
+ ```
307
+
308
+ ### 模块设计要点
309
+
310
+ | 要素 | 必须 | 说明 |
311
+ |------|------|------|
312
+ | `name` | ✅ | 唯一标识符,kebab-case |
313
+ | `version` | ✅ | 语义化版本 |
314
+ | `responsibility` | ✅ | 一句话描述职责 |
315
+ | `excludes` | ✅ | 明确列出不做的事 |
316
+ | `$ARGUMENTS` | ✅ | 支持命令行参数 |
317
+ | `confidence` | ✅ | 输出必须包含 0-1 置信度 |
318
+ | `rationale` | ✅ | 输出必须包含推理过程 |
319
+ | `schema.json` | ✅ | 定义输入输出契约 |
320
+
192
321
  ## 开发
193
322
 
194
323
  ```bash
@@ -202,7 +331,7 @@ pip install -e ".[dev]"
202
331
  # 运行测试
203
332
  pytest tests/ -v
204
333
 
205
- # 创建新模块
334
+ # 创建新模块(使用模板)
206
335
  cog init my-module -d "模块描述"
207
336
  cog validate my-module
208
337
  ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cognitive-modules"
7
- version = "0.1.1"
7
+ version = "0.3.0"
8
8
  description = "Structured LLM task runner with schema validation, confidence scoring, and subagent orchestration"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,258 @@
1
+ """
2
+ Module Loader - Load cognitive modules in all formats.
3
+
4
+ Format v2 (recommended):
5
+ - module.yaml (machine-readable manifest)
6
+ - prompt.md (human-readable prompt)
7
+ - schema.json (input + output + error)
8
+ - tests/ (golden tests)
9
+
10
+ Format v1 (legacy, still supported):
11
+ - MODULE.md (YAML frontmatter + prompt)
12
+ - schema.json (input + output)
13
+
14
+ Format v0 (old, deprecated):
15
+ - module.md (YAML frontmatter)
16
+ - input.schema.json
17
+ - output.schema.json
18
+ - constraints.yaml
19
+ - prompt.txt
20
+ """
21
+
22
+ import json
23
+ from pathlib import Path
24
+ from typing import Optional
25
+
26
+ import yaml
27
+
28
+
29
+ def detect_format(module_path: Path) -> str:
30
+ """Detect module format: 'v2', 'v1', or 'v0'."""
31
+ if (module_path / "module.yaml").exists():
32
+ return "v2"
33
+ elif (module_path / "MODULE.md").exists():
34
+ return "v1"
35
+ elif (module_path / "module.md").exists():
36
+ return "v0"
37
+ else:
38
+ raise FileNotFoundError(f"No module.yaml, MODULE.md, or module.md found in {module_path}")
39
+
40
+
41
+ def parse_frontmatter(content: str) -> tuple[dict, str]:
42
+ """Parse YAML frontmatter from markdown content."""
43
+ if not content.startswith('---'):
44
+ return {}, content
45
+
46
+ parts = content.split('---', 2)
47
+ if len(parts) < 3:
48
+ return {}, content
49
+
50
+ frontmatter = yaml.safe_load(parts[1]) or {}
51
+ body = parts[2].strip()
52
+ return frontmatter, body
53
+
54
+
55
+ def load_v2_format(module_path: Path) -> dict:
56
+ """Load module in v2 format (module.yaml + prompt.md + schema.json)."""
57
+ # Load module.yaml
58
+ with open(module_path / "module.yaml", 'r', encoding='utf-8') as f:
59
+ manifest = yaml.safe_load(f)
60
+
61
+ # Load prompt.md
62
+ prompt_path = module_path / "prompt.md"
63
+ if prompt_path.exists():
64
+ with open(prompt_path, 'r', encoding='utf-8') as f:
65
+ prompt = f.read()
66
+ else:
67
+ prompt = ""
68
+
69
+ # Load schema.json
70
+ schema_path = module_path / "schema.json"
71
+ if schema_path.exists():
72
+ with open(schema_path, 'r', encoding='utf-8') as f:
73
+ schema = json.load(f)
74
+ input_schema = schema.get("input", {})
75
+ output_schema = schema.get("output", {})
76
+ error_schema = schema.get("error", {})
77
+ else:
78
+ input_schema = {}
79
+ output_schema = {}
80
+ error_schema = {}
81
+
82
+ # Extract constraints (supports both old and new format)
83
+ constraints_raw = manifest.get("constraints", {})
84
+ policies_raw = manifest.get("policies", {})
85
+
86
+ constraints = {
87
+ "operational": {
88
+ "no_external_network": constraints_raw.get("no_network", True) or policies_raw.get("network") == "deny",
89
+ "no_side_effects": constraints_raw.get("no_side_effects", True) or policies_raw.get("side_effects") == "deny",
90
+ "no_file_write": constraints_raw.get("no_file_write", True) or policies_raw.get("filesystem_write") == "deny",
91
+ "no_inventing_data": constraints_raw.get("no_inventing_data", True),
92
+ },
93
+ "output_quality": {
94
+ "require_confidence": manifest.get("output", {}).get("require_confidence", True),
95
+ "require_rationale": manifest.get("output", {}).get("require_rationale", True),
96
+ "require_behavior_equivalence": manifest.get("output", {}).get("require_behavior_equivalence", False),
97
+ },
98
+ "behavior_equivalence_false_max_confidence": constraints_raw.get("behavior_equivalence_false_max_confidence", 0.7),
99
+ }
100
+
101
+ # Extract policies (v2.1)
102
+ policies = manifest.get("policies", {})
103
+
104
+ # Extract tools policy
105
+ tools = manifest.get("tools", {})
106
+
107
+ # Extract output contract
108
+ output_contract = manifest.get("output", {})
109
+
110
+ # Extract failure contract
111
+ failure_contract = manifest.get("failure", {})
112
+
113
+ # Extract runtime requirements
114
+ runtime_requirements = manifest.get("runtime_requirements", {})
115
+
116
+ return {
117
+ "name": manifest.get("name", module_path.name),
118
+ "version": manifest.get("version", "1.0.0"),
119
+ "responsibility": manifest.get("responsibility", ""),
120
+ "excludes": manifest.get("excludes", []),
121
+ "path": module_path,
122
+ "format": "v2",
123
+ "metadata": manifest,
124
+ "input_schema": input_schema,
125
+ "output_schema": output_schema,
126
+ "error_schema": error_schema,
127
+ "constraints": constraints,
128
+ "policies": policies,
129
+ "tools": tools,
130
+ "output_contract": output_contract,
131
+ "failure_contract": failure_contract,
132
+ "runtime_requirements": runtime_requirements,
133
+ "prompt": prompt,
134
+ }
135
+
136
+
137
+ def load_v1_format(module_path: Path) -> dict:
138
+ """Load module in v1 format (MODULE.md + schema.json)."""
139
+ # Load MODULE.md
140
+ with open(module_path / "MODULE.md", 'r', encoding='utf-8') as f:
141
+ content = f.read()
142
+
143
+ metadata, prompt = parse_frontmatter(content)
144
+
145
+ # Extract constraints from metadata
146
+ constraints = {
147
+ "operational": {
148
+ "no_external_network": metadata.get("constraints", {}).get("no_network", True),
149
+ "no_side_effects": metadata.get("constraints", {}).get("no_side_effects", True),
150
+ "no_inventing_data": metadata.get("constraints", {}).get("no_inventing_data", True),
151
+ },
152
+ "output_quality": {
153
+ "require_confidence": metadata.get("constraints", {}).get("require_confidence", True),
154
+ "require_rationale": metadata.get("constraints", {}).get("require_rationale", True),
155
+ }
156
+ }
157
+
158
+ # Load schema.json
159
+ schema_path = module_path / "schema.json"
160
+ if schema_path.exists():
161
+ with open(schema_path, 'r', encoding='utf-8') as f:
162
+ schema = json.load(f)
163
+ input_schema = schema.get("input", {})
164
+ output_schema = schema.get("output", {})
165
+ else:
166
+ input_schema = {}
167
+ output_schema = {}
168
+
169
+ return {
170
+ "name": metadata.get("name", module_path.name),
171
+ "version": metadata.get("version", "1.0.0"),
172
+ "responsibility": metadata.get("responsibility", ""),
173
+ "excludes": metadata.get("excludes", []),
174
+ "path": module_path,
175
+ "format": "v1",
176
+ "metadata": metadata,
177
+ "input_schema": input_schema,
178
+ "output_schema": output_schema,
179
+ "constraints": constraints,
180
+ "prompt": prompt,
181
+ }
182
+
183
+
184
+ def load_v0_format(module_path: Path) -> dict:
185
+ """Load module in v0 format (old 6-file format)."""
186
+ # Load module.md
187
+ with open(module_path / "module.md", 'r', encoding='utf-8') as f:
188
+ content = f.read()
189
+
190
+ metadata, _ = parse_frontmatter(content)
191
+
192
+ # Load schemas
193
+ with open(module_path / "input.schema.json", 'r', encoding='utf-8') as f:
194
+ input_schema = json.load(f)
195
+
196
+ with open(module_path / "output.schema.json", 'r', encoding='utf-8') as f:
197
+ output_schema = json.load(f)
198
+
199
+ # Load constraints
200
+ with open(module_path / "constraints.yaml", 'r', encoding='utf-8') as f:
201
+ constraints = yaml.safe_load(f)
202
+
203
+ # Load prompt
204
+ with open(module_path / "prompt.txt", 'r', encoding='utf-8') as f:
205
+ prompt = f.read()
206
+
207
+ return {
208
+ "name": metadata.get("name", module_path.name),
209
+ "version": metadata.get("version", "1.0.0"),
210
+ "responsibility": metadata.get("responsibility", ""),
211
+ "excludes": [],
212
+ "path": module_path,
213
+ "format": "v0",
214
+ "metadata": metadata,
215
+ "input_schema": input_schema,
216
+ "output_schema": output_schema,
217
+ "constraints": constraints,
218
+ "prompt": prompt,
219
+ }
220
+
221
+
222
+ def load_module(module_path: Path) -> dict:
223
+ """Load a module, auto-detecting format."""
224
+ fmt = detect_format(module_path)
225
+ if fmt == "v2":
226
+ return load_v2_format(module_path)
227
+ elif fmt == "v1":
228
+ return load_v1_format(module_path)
229
+ else:
230
+ return load_v0_format(module_path)
231
+
232
+
233
+ def find_module(name: str, search_paths: list[Path]) -> Optional[dict]:
234
+ """Find and load a module by name from search paths."""
235
+ for base_path in search_paths:
236
+ module_path = base_path / name
237
+ if module_path.exists():
238
+ try:
239
+ return load_module(module_path)
240
+ except FileNotFoundError:
241
+ continue
242
+ return None
243
+
244
+
245
+ def list_modules(search_paths: list[Path]) -> list[dict]:
246
+ """List all modules in search paths."""
247
+ modules = []
248
+ for base_path in search_paths:
249
+ if not base_path.exists():
250
+ continue
251
+ for module_dir in base_path.iterdir():
252
+ if module_dir.is_dir():
253
+ try:
254
+ module = load_module(module_dir)
255
+ modules.append(module)
256
+ except FileNotFoundError:
257
+ continue
258
+ return modules
@@ -0,0 +1,276 @@
1
+ """
2
+ Module Runner - Execute cognitive modules with validation.
3
+ Supports v2 envelope format and legacy formats.
4
+ """
5
+
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Optional, TypedDict, Union
9
+
10
+ import jsonschema
11
+ import yaml
12
+
13
+ from .registry import find_module
14
+ from .loader import load_module
15
+ from .providers import call_llm
16
+
17
+
18
+ class EnvelopeError(TypedDict):
19
+ code: str
20
+ message: str
21
+
22
+
23
+ class EnvelopeSuccess(TypedDict):
24
+ ok: bool # True
25
+ data: dict
26
+
27
+
28
+ class EnvelopeFailure(TypedDict):
29
+ ok: bool # False
30
+ error: EnvelopeError
31
+ partial_data: Optional[dict]
32
+
33
+
34
+ EnvelopeResponse = Union[EnvelopeSuccess, EnvelopeFailure]
35
+
36
+
37
+ def validate_data(data: dict, schema: dict, label: str = "Data") -> list[str]:
38
+ """Validate data against schema. Returns list of errors."""
39
+ errors = []
40
+ if not schema:
41
+ return errors
42
+ try:
43
+ jsonschema.validate(instance=data, schema=schema)
44
+ except jsonschema.ValidationError as e:
45
+ errors.append(f"{label} validation error: {e.message} at {list(e.absolute_path)}")
46
+ except jsonschema.SchemaError as e:
47
+ errors.append(f"Schema error: {e.message}")
48
+ return errors
49
+
50
+
51
+ def substitute_arguments(text: str, input_data: dict) -> str:
52
+ """Substitute $ARGUMENTS and $N placeholders in text."""
53
+ # Get arguments
54
+ args_value = input_data.get("$ARGUMENTS", input_data.get("query", input_data.get("code", "")))
55
+
56
+ # Replace $ARGUMENTS
57
+ text = text.replace("$ARGUMENTS", str(args_value))
58
+
59
+ # Replace $ARGUMENTS[N] and $N for indexed access
60
+ if isinstance(args_value, str):
61
+ args_list = args_value.split()
62
+ for i, arg in enumerate(args_list):
63
+ text = text.replace(f"$ARGUMENTS[{i}]", arg)
64
+ text = text.replace(f"${i}", arg)
65
+
66
+ return text
67
+
68
+
69
+ def build_prompt(module: dict, input_data: dict, use_envelope: bool = False) -> str:
70
+ """Build the complete prompt for the LLM."""
71
+ # Substitute $ARGUMENTS in prompt
72
+ prompt = substitute_arguments(module["prompt"], input_data)
73
+
74
+ parts = [
75
+ prompt,
76
+ "\n\n## Constraints\n",
77
+ yaml.dump(module["constraints"], default_flow_style=False),
78
+ "\n\n## Input\n",
79
+ "```json\n",
80
+ json.dumps(input_data, indent=2, ensure_ascii=False),
81
+ "\n```\n",
82
+ ]
83
+
84
+ if use_envelope:
85
+ parts.extend([
86
+ "\n## Response Format (Envelope)\n",
87
+ "You MUST wrap your response in the envelope format:\n",
88
+ "- Success: { \"ok\": true, \"data\": { ...your output... } }\n",
89
+ "- Error: { \"ok\": false, \"error\": { \"code\": \"ERROR_CODE\", \"message\": \"...\" } }\n",
90
+ "Return ONLY valid JSON.\n",
91
+ ])
92
+ else:
93
+ parts.extend([
94
+ "\n## Instructions\n",
95
+ "Analyze the input and generate output matching the required schema.",
96
+ "Return ONLY valid JSON. Do not include any text before or after the JSON.",
97
+ ])
98
+
99
+ return "".join(parts)
100
+
101
+
102
+ def parse_llm_response(response: str) -> dict:
103
+ """Parse LLM response, handling potential markdown code blocks."""
104
+ text = response.strip()
105
+
106
+ # Remove markdown code blocks if present
107
+ if text.startswith("```"):
108
+ lines = text.split("\n")
109
+ start = 1
110
+ end = len(lines) - 1
111
+ for i, line in enumerate(lines[1:], 1):
112
+ if line.strip() == "```":
113
+ end = i
114
+ break
115
+ text = "\n".join(lines[start:end])
116
+
117
+ return json.loads(text)
118
+
119
+
120
+ def is_envelope_response(data: dict) -> bool:
121
+ """Check if response is in envelope format."""
122
+ return isinstance(data.get("ok"), bool)
123
+
124
+
125
+ def parse_envelope_response(data: dict) -> EnvelopeResponse:
126
+ """Parse and normalize envelope response."""
127
+ if data.get("ok") is True:
128
+ return {
129
+ "ok": True,
130
+ "data": data.get("data", {})
131
+ }
132
+ else:
133
+ return {
134
+ "ok": False,
135
+ "error": data.get("error", {"code": "UNKNOWN", "message": "Unknown error"}),
136
+ "partial_data": data.get("partial_data")
137
+ }
138
+
139
+
140
+ def convert_to_envelope(data: dict, is_error: bool = False) -> EnvelopeResponse:
141
+ """Convert legacy format to envelope format."""
142
+ if is_error or "error" in data:
143
+ error = data.get("error", {})
144
+ return {
145
+ "ok": False,
146
+ "error": {
147
+ "code": error.get("code", "UNKNOWN"),
148
+ "message": error.get("message", str(error))
149
+ },
150
+ "partial_data": None
151
+ }
152
+ else:
153
+ return {
154
+ "ok": True,
155
+ "data": data
156
+ }
157
+
158
+
159
+ def run_module(
160
+ name_or_path: str,
161
+ input_data: dict,
162
+ validate_input: bool = True,
163
+ validate_output: bool = True,
164
+ model: Optional[str] = None,
165
+ use_envelope: Optional[bool] = None,
166
+ ) -> EnvelopeResponse:
167
+ """
168
+ Run a cognitive module with the given input.
169
+ Returns envelope format response.
170
+
171
+ Args:
172
+ name_or_path: Module name or path to module directory
173
+ input_data: Input data dictionary
174
+ validate_input: Whether to validate input against schema
175
+ validate_output: Whether to validate output against schema
176
+ model: Optional model override
177
+ use_envelope: Force envelope format (auto-detect if None)
178
+
179
+ Returns:
180
+ EnvelopeResponse with ok=True/False and data/error
181
+ """
182
+ # Find module path
183
+ path = Path(name_or_path)
184
+ if path.exists() and path.is_dir():
185
+ module_path = path
186
+ else:
187
+ module_path = find_module(name_or_path)
188
+ if not module_path:
189
+ return {
190
+ "ok": False,
191
+ "error": {"code": "MODULE_NOT_FOUND", "message": f"Module not found: {name_or_path}"},
192
+ "partial_data": None
193
+ }
194
+
195
+ # Load module (auto-detects format)
196
+ module = load_module(module_path)
197
+
198
+ # Determine if we should use envelope format
199
+ should_use_envelope = use_envelope
200
+ if should_use_envelope is None:
201
+ # Auto-detect: use envelope for v2 format or if output.envelope is True
202
+ output_contract = module.get("output_contract", {})
203
+ should_use_envelope = (
204
+ module.get("format") == "v2" or
205
+ output_contract.get("envelope", False)
206
+ )
207
+
208
+ # Validate input
209
+ if validate_input and module["input_schema"]:
210
+ errors = validate_data(input_data, module["input_schema"], "Input")
211
+ if errors:
212
+ return {
213
+ "ok": False,
214
+ "error": {"code": "INVALID_INPUT", "message": str(errors)},
215
+ "partial_data": None
216
+ }
217
+
218
+ # Build prompt and call LLM
219
+ full_prompt = build_prompt(module, input_data, use_envelope=should_use_envelope)
220
+ response = call_llm(full_prompt, model=model)
221
+
222
+ # Parse response
223
+ try:
224
+ output_data = parse_llm_response(response)
225
+ except json.JSONDecodeError as e:
226
+ return {
227
+ "ok": False,
228
+ "error": {"code": "PARSE_ERROR", "message": f"Failed to parse JSON: {e}"},
229
+ "partial_data": None
230
+ }
231
+
232
+ # Handle envelope format
233
+ if is_envelope_response(output_data):
234
+ result = parse_envelope_response(output_data)
235
+ else:
236
+ # Convert legacy format to envelope
237
+ result = convert_to_envelope(output_data)
238
+
239
+ # Validate output (only for success responses)
240
+ if result["ok"] and validate_output and module["output_schema"]:
241
+ data_to_validate = result.get("data", {})
242
+ errors = validate_data(data_to_validate, module["output_schema"], "Output")
243
+ if errors:
244
+ return {
245
+ "ok": False,
246
+ "error": {"code": "OUTPUT_VALIDATION_ERROR", "message": str(errors)},
247
+ "partial_data": data_to_validate
248
+ }
249
+
250
+ return result
251
+
252
+
253
+ def run_module_legacy(
254
+ name_or_path: str,
255
+ input_data: dict,
256
+ validate_input: bool = True,
257
+ validate_output: bool = True,
258
+ model: Optional[str] = None,
259
+ ) -> dict:
260
+ """
261
+ Run a cognitive module (legacy API, returns raw output).
262
+ For backward compatibility.
263
+ """
264
+ result = run_module(
265
+ name_or_path,
266
+ input_data,
267
+ validate_input=validate_input,
268
+ validate_output=validate_output,
269
+ model=model,
270
+ use_envelope=False
271
+ )
272
+
273
+ if result["ok"]:
274
+ return result["data"]
275
+ else:
276
+ raise ValueError(f"{result['error']['code']}: {result['error']['message']}")
@@ -83,7 +83,7 @@ EXAMPLE_OUTPUT = {
83
83
  def get_schema_template(name: str) -> dict:
84
84
  """Generate schema template as dict."""
85
85
  return {
86
- "$schema": "https://cognitive-modules.io/schema/v1",
86
+ "$schema": "https://ziel-io.github.io/cognitive-modules/schema/v1.json",
87
87
  "$id": name,
88
88
  "title": f"{name.replace('-', ' ').title()} Schema",
89
89
  "input": {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cognitive-modules
3
- Version: 0.1.1
3
+ Version: 0.3.0
4
4
  Summary: Structured LLM task runner with schema validation, confidence scoring, and subagent orchestration
5
5
  Author: ziel-io
6
6
  License: MIT
@@ -151,6 +151,7 @@ cog doctor
151
151
  | 模块 | 功能 | 示例 |
152
152
  |------|------|------|
153
153
  | `code-reviewer` | 代码审查 | `cog run code-reviewer --args "你的代码"` |
154
+ | `code-simplifier` | 代码简化 | `cog run code-simplifier --args "复杂代码"` |
154
155
  | `task-prioritizer` | 任务优先级排序 | `cog run task-prioritizer --args "任务1,任务2"` |
155
156
  | `api-designer` | REST API 设计 | `cog run api-designer --args "订单系统"` |
156
157
  | `ui-spec-generator` | UI 规范生成 | `cog run ui-spec-generator --args "电商首页"` |
@@ -241,6 +242,134 @@ export LLM_PROVIDER=ollama
241
242
  cog doctor
242
243
  ```
243
244
 
245
+ ## 创建新模块(完整流程)
246
+
247
+ 以 `code-simplifier` 为例:
248
+
249
+ ### Step 1: 创建目录结构
250
+
251
+ ```bash
252
+ mkdir -p cognitive/modules/code-simplifier
253
+ ```
254
+
255
+ ### Step 2: 编写 MODULE.md
256
+
257
+ ```bash
258
+ cat > cognitive/modules/code-simplifier/MODULE.md << 'EOF'
259
+ ---
260
+ name: code-simplifier
261
+ version: 1.0.0
262
+ responsibility: Simplify complex code while preserving functionality
263
+
264
+ excludes:
265
+ - Changing the code's behavior
266
+ - Adding new features
267
+ - Removing functionality
268
+
269
+ constraints:
270
+ no_network: true
271
+ no_side_effects: true
272
+ require_confidence: true
273
+ require_rationale: true
274
+ ---
275
+
276
+ # Code Simplifier Module
277
+
278
+ You are an expert at refactoring and simplifying code.
279
+
280
+ ## Input
281
+
282
+ Code to simplify: $ARGUMENTS
283
+
284
+ ## Simplification Strategies
285
+
286
+ 1. **Remove redundancy** - Eliminate duplicate code
287
+ 2. **Improve naming** - Use clear, descriptive names
288
+ 3. **Reduce nesting** - Flatten deep conditionals
289
+ 4. **Simplify logic** - Use built-in functions
290
+
291
+ ## Output Requirements
292
+
293
+ Return JSON containing:
294
+ - `simplified_code`: The simplified version
295
+ - `changes`: List of changes made
296
+ - `summary`: Brief description
297
+ - `rationale`: Explanation of decisions
298
+ - `confidence`: Confidence score [0-1]
299
+ EOF
300
+ ```
301
+
302
+ ### Step 3: 编写 schema.json
303
+
304
+ ```bash
305
+ cat > cognitive/modules/code-simplifier/schema.json << 'EOF'
306
+ {
307
+ "$schema": "https://ziel-io.github.io/cognitive-modules/schema/v1.json",
308
+ "input": {
309
+ "type": "object",
310
+ "properties": {
311
+ "code": { "type": "string" },
312
+ "language": { "type": "string" },
313
+ "$ARGUMENTS": { "type": "string" }
314
+ }
315
+ },
316
+ "output": {
317
+ "type": "object",
318
+ "required": ["simplified_code", "changes", "summary", "rationale", "confidence"],
319
+ "properties": {
320
+ "simplified_code": { "type": "string" },
321
+ "changes": {
322
+ "type": "array",
323
+ "items": {
324
+ "type": "object",
325
+ "properties": {
326
+ "type": { "type": "string" },
327
+ "description": { "type": "string" }
328
+ }
329
+ }
330
+ },
331
+ "summary": { "type": "string" },
332
+ "rationale": { "type": "string" },
333
+ "confidence": { "type": "number", "minimum": 0, "maximum": 1 }
334
+ }
335
+ }
336
+ }
337
+ EOF
338
+ ```
339
+
340
+ ### Step 4: 验证模块
341
+
342
+ ```bash
343
+ cog validate code-simplifier
344
+ cog list # 确认模块出现在列表中
345
+ ```
346
+
347
+ ### Step 5: 测试运行
348
+
349
+ ```bash
350
+ cog run code-simplifier --args "def calc(x): if x > 0: if x < 10: return x * 2 else: return x else: return 0" --pretty
351
+ ```
352
+
353
+ ### Step 6: 添加示例(可选)
354
+
355
+ ```bash
356
+ mkdir -p cognitive/modules/code-simplifier/examples
357
+ # 添加 input.json 和 output.json 作为测试用例
358
+ ```
359
+
360
+ ### 模块设计要点
361
+
362
+ | 要素 | 必须 | 说明 |
363
+ |------|------|------|
364
+ | `name` | ✅ | 唯一标识符,kebab-case |
365
+ | `version` | ✅ | 语义化版本 |
366
+ | `responsibility` | ✅ | 一句话描述职责 |
367
+ | `excludes` | ✅ | 明确列出不做的事 |
368
+ | `$ARGUMENTS` | ✅ | 支持命令行参数 |
369
+ | `confidence` | ✅ | 输出必须包含 0-1 置信度 |
370
+ | `rationale` | ✅ | 输出必须包含推理过程 |
371
+ | `schema.json` | ✅ | 定义输入输出契约 |
372
+
244
373
  ## 开发
245
374
 
246
375
  ```bash
@@ -254,7 +383,7 @@ pip install -e ".[dev]"
254
383
  # 运行测试
255
384
  pytest tests/ -v
256
385
 
257
- # 创建新模块
386
+ # 创建新模块(使用模板)
258
387
  cog init my-module -d "模块描述"
259
388
  cog validate my-module
260
389
  ```
@@ -1,133 +0,0 @@
1
- """
2
- Module Loader - Load cognitive modules in both old and new formats.
3
-
4
- Old format (6 files):
5
- - module.md (YAML frontmatter)
6
- - input.schema.json
7
- - output.schema.json
8
- - constraints.yaml
9
- - prompt.txt
10
- - examples/
11
-
12
- New format (2 files):
13
- - MODULE.md (YAML frontmatter + prompt)
14
- - schema.json (input + output combined)
15
- """
16
-
17
- import json
18
- from pathlib import Path
19
- from typing import Optional
20
-
21
- import yaml
22
-
23
-
24
- def detect_format(module_path: Path) -> str:
25
- """Detect module format: 'new' or 'old'."""
26
- if (module_path / "MODULE.md").exists():
27
- return "new"
28
- elif (module_path / "module.md").exists():
29
- return "old"
30
- else:
31
- raise FileNotFoundError(f"No MODULE.md or module.md found in {module_path}")
32
-
33
-
34
- def parse_frontmatter(content: str) -> tuple[dict, str]:
35
- """Parse YAML frontmatter from markdown content."""
36
- if not content.startswith('---'):
37
- return {}, content
38
-
39
- parts = content.split('---', 2)
40
- if len(parts) < 3:
41
- return {}, content
42
-
43
- frontmatter = yaml.safe_load(parts[1]) or {}
44
- body = parts[2].strip()
45
- return frontmatter, body
46
-
47
-
48
- def load_new_format(module_path: Path) -> dict:
49
- """Load module in new format (MODULE.md + schema.json)."""
50
- # Load MODULE.md
51
- with open(module_path / "MODULE.md", 'r', encoding='utf-8') as f:
52
- content = f.read()
53
-
54
- metadata, prompt = parse_frontmatter(content)
55
-
56
- # Extract constraints from metadata
57
- constraints = {
58
- "operational": {
59
- "no_external_network": metadata.get("constraints", {}).get("no_network", True),
60
- "no_side_effects": metadata.get("constraints", {}).get("no_side_effects", True),
61
- "no_inventing_data": metadata.get("constraints", {}).get("no_inventing_data", True),
62
- },
63
- "output_quality": {
64
- "require_confidence": metadata.get("constraints", {}).get("require_confidence", True),
65
- "require_rationale": metadata.get("constraints", {}).get("require_rationale", True),
66
- }
67
- }
68
-
69
- # Load schema.json
70
- schema_path = module_path / "schema.json"
71
- if schema_path.exists():
72
- with open(schema_path, 'r', encoding='utf-8') as f:
73
- schema = json.load(f)
74
- input_schema = schema.get("input", {})
75
- output_schema = schema.get("output", {})
76
- else:
77
- input_schema = {}
78
- output_schema = {}
79
-
80
- return {
81
- "name": metadata.get("name", module_path.name),
82
- "path": module_path,
83
- "format": "new",
84
- "metadata": metadata,
85
- "input_schema": input_schema,
86
- "output_schema": output_schema,
87
- "constraints": constraints,
88
- "prompt": prompt,
89
- }
90
-
91
-
92
- def load_old_format(module_path: Path) -> dict:
93
- """Load module in old format (6 files)."""
94
- # Load module.md
95
- with open(module_path / "module.md", 'r', encoding='utf-8') as f:
96
- content = f.read()
97
-
98
- metadata, _ = parse_frontmatter(content)
99
-
100
- # Load schemas
101
- with open(module_path / "input.schema.json", 'r', encoding='utf-8') as f:
102
- input_schema = json.load(f)
103
-
104
- with open(module_path / "output.schema.json", 'r', encoding='utf-8') as f:
105
- output_schema = json.load(f)
106
-
107
- # Load constraints
108
- with open(module_path / "constraints.yaml", 'r', encoding='utf-8') as f:
109
- constraints = yaml.safe_load(f)
110
-
111
- # Load prompt
112
- with open(module_path / "prompt.txt", 'r', encoding='utf-8') as f:
113
- prompt = f.read()
114
-
115
- return {
116
- "name": metadata.get("name", module_path.name),
117
- "path": module_path,
118
- "format": "old",
119
- "metadata": metadata,
120
- "input_schema": input_schema,
121
- "output_schema": output_schema,
122
- "constraints": constraints,
123
- "prompt": prompt,
124
- }
125
-
126
-
127
- def load_module(module_path: Path) -> dict:
128
- """Load a module, auto-detecting format."""
129
- fmt = detect_format(module_path)
130
- if fmt == "new":
131
- return load_new_format(module_path)
132
- else:
133
- return load_old_format(module_path)
@@ -1,140 +0,0 @@
1
- """
2
- Module Runner - Execute cognitive modules with validation.
3
- Supports both old and new module formats.
4
- """
5
-
6
- import json
7
- from pathlib import Path
8
- from typing import Optional
9
-
10
- import jsonschema
11
- import yaml
12
-
13
- from .registry import find_module
14
- from .loader import load_module
15
- from .providers import call_llm
16
-
17
-
18
- def validate_data(data: dict, schema: dict, label: str = "Data") -> list[str]:
19
- """Validate data against schema. Returns list of errors."""
20
- errors = []
21
- if not schema:
22
- return errors
23
- try:
24
- jsonschema.validate(instance=data, schema=schema)
25
- except jsonschema.ValidationError as e:
26
- errors.append(f"{label} validation error: {e.message} at {list(e.absolute_path)}")
27
- except jsonschema.SchemaError as e:
28
- errors.append(f"Schema error: {e.message}")
29
- return errors
30
-
31
-
32
- def substitute_arguments(text: str, input_data: dict) -> str:
33
- """Substitute $ARGUMENTS and $N placeholders in text."""
34
- # Get arguments
35
- args_value = input_data.get("$ARGUMENTS", input_data.get("query", ""))
36
-
37
- # Replace $ARGUMENTS
38
- text = text.replace("$ARGUMENTS", str(args_value))
39
-
40
- # Replace $ARGUMENTS[N] and $N for indexed access
41
- if isinstance(args_value, str):
42
- args_list = args_value.split()
43
- for i, arg in enumerate(args_list):
44
- text = text.replace(f"$ARGUMENTS[{i}]", arg)
45
- text = text.replace(f"${i}", arg)
46
-
47
- return text
48
-
49
-
50
- def build_prompt(module: dict, input_data: dict) -> str:
51
- """Build the complete prompt for the LLM."""
52
- # Substitute $ARGUMENTS in prompt
53
- prompt = substitute_arguments(module["prompt"], input_data)
54
-
55
- parts = [
56
- prompt,
57
- "\n\n## Constraints\n",
58
- yaml.dump(module["constraints"], default_flow_style=False),
59
- "\n\n## Input\n",
60
- "```json\n",
61
- json.dumps(input_data, indent=2, ensure_ascii=False),
62
- "\n```\n",
63
- "\n## Instructions\n",
64
- "Analyze the input and generate output matching the required schema.",
65
- "Return ONLY valid JSON. Do not include any text before or after the JSON.",
66
- ]
67
- return "".join(parts)
68
-
69
-
70
- def parse_llm_response(response: str) -> dict:
71
- """Parse LLM response, handling potential markdown code blocks."""
72
- text = response.strip()
73
-
74
- # Remove markdown code blocks if present
75
- if text.startswith("```"):
76
- lines = text.split("\n")
77
- start = 1
78
- end = len(lines) - 1
79
- for i, line in enumerate(lines[1:], 1):
80
- if line.strip() == "```":
81
- end = i
82
- break
83
- text = "\n".join(lines[start:end])
84
-
85
- return json.loads(text)
86
-
87
-
88
- def run_module(
89
- name_or_path: str,
90
- input_data: dict,
91
- validate_input: bool = True,
92
- validate_output: bool = True,
93
- model: Optional[str] = None,
94
- ) -> dict:
95
- """
96
- Run a cognitive module with the given input.
97
- Supports both old and new module formats.
98
-
99
- Args:
100
- name_or_path: Module name or path to module directory
101
- input_data: Input data dictionary
102
- validate_input: Whether to validate input against schema
103
- validate_output: Whether to validate output against schema
104
- model: Optional model override
105
-
106
- Returns:
107
- The module output as a dictionary
108
- """
109
- # Find module path
110
- path = Path(name_or_path)
111
- if path.exists() and path.is_dir():
112
- module_path = path
113
- else:
114
- module_path = find_module(name_or_path)
115
- if not module_path:
116
- raise FileNotFoundError(f"Module not found: {name_or_path}")
117
-
118
- # Load module (auto-detects format)
119
- module = load_module(module_path)
120
-
121
- # Validate input
122
- if validate_input and module["input_schema"]:
123
- errors = validate_data(input_data, module["input_schema"], "Input")
124
- if errors:
125
- raise ValueError(f"Input validation failed: {errors}")
126
-
127
- # Build prompt and call LLM
128
- full_prompt = build_prompt(module, input_data)
129
- response = call_llm(full_prompt, model=model)
130
-
131
- # Parse response
132
- output_data = parse_llm_response(response)
133
-
134
- # Validate output
135
- if validate_output and module["output_schema"]:
136
- errors = validate_data(output_data, module["output_schema"], "Output")
137
- if errors:
138
- raise ValueError(f"Output validation failed: {errors}")
139
-
140
- return output_data