agentic 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/.agentic.yml +2 -0
- data/.architecture/decisions/ArchitecturalFeatureBuilder.md +136 -0
- data/.architecture/decisions/ArchitectureConsiderations.md +200 -0
- data/.architecture/decisions/adr_001_observer_pattern_implementation.md +196 -0
- data/.architecture/decisions/adr_002_plan_orchestrator.md +320 -0
- data/.architecture/decisions/adr_003_plan_orchestrator_interface.md +179 -0
- data/.architecture/decisions/adrs/ADR-001-dependency-management.md +147 -0
- data/.architecture/decisions/adrs/ADR-002-system-boundaries.md +162 -0
- data/.architecture/decisions/adrs/ADR-003-content-safety.md +158 -0
- data/.architecture/decisions/adrs/ADR-004-agent-permissions.md +161 -0
- data/.architecture/decisions/adrs/ADR-005-adaptation-engine.md +127 -0
- data/.architecture/decisions/adrs/ADR-006-extension-system.md +273 -0
- data/.architecture/decisions/adrs/ADR-007-learning-system.md +156 -0
- data/.architecture/decisions/adrs/ADR-008-prompt-generation.md +325 -0
- data/.architecture/decisions/adrs/ADR-009-task-failure-handling.md +353 -0
- data/.architecture/decisions/adrs/ADR-010-task-input-handling.md +251 -0
- data/.architecture/decisions/adrs/ADR-011-task-observable-pattern.md +391 -0
- data/.architecture/decisions/adrs/ADR-012-task-output-handling.md +205 -0
- data/.architecture/decisions/adrs/ADR-013-architecture-alignment.md +211 -0
- data/.architecture/decisions/adrs/ADR-014-agent-capability-registry.md +80 -0
- data/.architecture/decisions/adrs/ADR-015-persistent-agent-store.md +100 -0
- data/.architecture/decisions/adrs/ADR-016-agent-assembly-engine.md +117 -0
- data/.architecture/decisions/adrs/ADR-017-streaming-observability.md +171 -0
- data/.architecture/decisions/capability_tools_distinction.md +150 -0
- data/.architecture/decisions/cli_command_structure.md +61 -0
- data/.architecture/implementation/agent_self_assembly_implementation.md +267 -0
- data/.architecture/implementation/agent_self_assembly_summary.md +138 -0
- data/.architecture/members.yml +187 -0
- data/.architecture/planning/self_implementation_exercise.md +295 -0
- data/.architecture/planning/session_compaction_rule.md +43 -0
- data/.architecture/planning/streaming_observability_feature.md +223 -0
- data/.architecture/principles.md +151 -0
- data/.architecture/recalibration/0-2-0.md +92 -0
- data/.architecture/recalibration/agent_self_assembly.md +238 -0
- data/.architecture/recalibration/cli_command_structure.md +91 -0
- data/.architecture/recalibration/implementation_roadmap_0-2-0.md +301 -0
- data/.architecture/recalibration/progress_tracking_0-2-0.md +114 -0
- data/.architecture/recalibration_process.md +127 -0
- data/.architecture/reviews/0-2-0.md +181 -0
- data/.architecture/reviews/cli_command_duplication.md +98 -0
- data/.architecture/templates/adr.md +105 -0
- data/.architecture/templates/implementation_roadmap.md +125 -0
- data/.architecture/templates/progress_tracking.md +89 -0
- data/.architecture/templates/recalibration_plan.md +70 -0
- data/.architecture/templates/version_comparison.md +124 -0
- data/.claude/settings.local.json +13 -0
- data/.claude-sessions/001-task-class-architecture-implementation.md +129 -0
- data/.claude-sessions/002-plan-orchestrator-interface-review.md +105 -0
- data/.claude-sessions/architecture-governance-implementation.md +37 -0
- data/.claude-sessions/architecture-review-session.md +27 -0
- data/ArchitecturalFeatureBuilder.md +136 -0
- data/ArchitectureConsiderations.md +229 -0
- data/CHANGELOG.md +57 -2
- data/CLAUDE.md +111 -0
- data/CONTRIBUTING.md +286 -0
- data/MAINTAINING.md +301 -0
- data/README.md +582 -28
- data/docs/agent_capabilities_api.md +259 -0
- data/docs/artifact_extension_points.md +757 -0
- data/docs/artifact_generation_architecture.md +323 -0
- data/docs/artifact_implementation_plan.md +596 -0
- data/docs/artifact_integration_points.md +345 -0
- data/docs/artifact_verification_strategies.md +581 -0
- data/docs/streaming_observability_architecture.md +510 -0
- data/exe/agentic +6 -1
- data/lefthook.yml +5 -0
- data/lib/agentic/adaptation_engine.rb +124 -0
- data/lib/agentic/agent.rb +181 -4
- data/lib/agentic/agent_assembly_engine.rb +442 -0
- data/lib/agentic/agent_capability_registry.rb +260 -0
- data/lib/agentic/agent_config.rb +63 -0
- data/lib/agentic/agent_specification.rb +46 -0
- data/lib/agentic/capabilities/examples.rb +530 -0
- data/lib/agentic/capabilities.rb +14 -0
- data/lib/agentic/capability_provider.rb +146 -0
- data/lib/agentic/capability_specification.rb +118 -0
- data/lib/agentic/cli/agent.rb +31 -0
- data/lib/agentic/cli/capabilities.rb +191 -0
- data/lib/agentic/cli/config.rb +134 -0
- data/lib/agentic/cli/execution_observer.rb +796 -0
- data/lib/agentic/cli.rb +1068 -0
- data/lib/agentic/default_agent_provider.rb +35 -0
- data/lib/agentic/errors/llm_error.rb +184 -0
- data/lib/agentic/execution_plan.rb +53 -0
- data/lib/agentic/execution_result.rb +91 -0
- data/lib/agentic/expected_answer_format.rb +46 -0
- data/lib/agentic/extension/domain_adapter.rb +109 -0
- data/lib/agentic/extension/plugin_manager.rb +163 -0
- data/lib/agentic/extension/protocol_handler.rb +116 -0
- data/lib/agentic/extension.rb +45 -0
- data/lib/agentic/factory_methods.rb +9 -1
- data/lib/agentic/generation_stats.rb +61 -0
- data/lib/agentic/learning/README.md +84 -0
- data/lib/agentic/learning/capability_optimizer.rb +613 -0
- data/lib/agentic/learning/execution_history_store.rb +251 -0
- data/lib/agentic/learning/pattern_recognizer.rb +500 -0
- data/lib/agentic/learning/strategy_optimizer.rb +706 -0
- data/lib/agentic/learning.rb +131 -0
- data/lib/agentic/llm_assisted_composition_strategy.rb +188 -0
- data/lib/agentic/llm_client.rb +215 -15
- data/lib/agentic/llm_config.rb +65 -1
- data/lib/agentic/llm_response.rb +163 -0
- data/lib/agentic/logger.rb +1 -1
- data/lib/agentic/observable.rb +51 -0
- data/lib/agentic/persistent_agent_store.rb +385 -0
- data/lib/agentic/plan_execution_result.rb +129 -0
- data/lib/agentic/plan_orchestrator.rb +464 -0
- data/lib/agentic/plan_orchestrator_config.rb +57 -0
- data/lib/agentic/retry_config.rb +63 -0
- data/lib/agentic/retry_handler.rb +125 -0
- data/lib/agentic/structured_outputs.rb +1 -1
- data/lib/agentic/task.rb +193 -0
- data/lib/agentic/task_definition.rb +39 -0
- data/lib/agentic/task_execution_result.rb +92 -0
- data/lib/agentic/task_failure.rb +66 -0
- data/lib/agentic/task_output_schemas.rb +112 -0
- data/lib/agentic/task_planner.rb +54 -19
- data/lib/agentic/task_result.rb +48 -0
- data/lib/agentic/ui.rb +244 -0
- data/lib/agentic/verification/critic_framework.rb +116 -0
- data/lib/agentic/verification/llm_verification_strategy.rb +60 -0
- data/lib/agentic/verification/schema_verification_strategy.rb +47 -0
- data/lib/agentic/verification/verification_hub.rb +62 -0
- data/lib/agentic/verification/verification_result.rb +50 -0
- data/lib/agentic/verification/verification_strategy.rb +26 -0
- data/lib/agentic/version.rb +1 -1
- data/lib/agentic.rb +74 -2
- data/plugins/README.md +41 -0
- metadata +245 -6
@@ -0,0 +1,530 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../capability_specification"
|
4
|
+
require_relative "../capability_provider"
|
5
|
+
require_relative "../agent_capability_registry"
|
6
|
+
|
7
|
+
module Agentic
|
8
|
+
module Capabilities
|
9
|
+
# Example capabilities for common tasks
|
10
|
+
module Examples
|
11
|
+
class << self
|
12
|
+
# Register all example capabilities
|
13
|
+
# @return [void]
|
14
|
+
def register_all
|
15
|
+
register_text_generation
|
16
|
+
register_web_search
|
17
|
+
register_data_analysis
|
18
|
+
register_code_generation
|
19
|
+
register_summarization
|
20
|
+
register_brainstorming
|
21
|
+
register_structured_extraction
|
22
|
+
end
|
23
|
+
|
24
|
+
# Register a text generation capability
|
25
|
+
# @return [CapabilitySpecification] The registered capability
|
26
|
+
def register_text_generation
|
27
|
+
spec = CapabilitySpecification.new(
|
28
|
+
name: "text_generation",
|
29
|
+
description: "Generates text based on a prompt",
|
30
|
+
version: "1.0.0",
|
31
|
+
inputs: {
|
32
|
+
prompt: {
|
33
|
+
type: "string",
|
34
|
+
required: true,
|
35
|
+
description: "The prompt to generate text from"
|
36
|
+
},
|
37
|
+
max_tokens: {
|
38
|
+
type: "integer",
|
39
|
+
description: "Maximum number of tokens to generate"
|
40
|
+
},
|
41
|
+
temperature: {
|
42
|
+
type: "number",
|
43
|
+
description: "Sampling temperature (0.0-1.0)"
|
44
|
+
}
|
45
|
+
},
|
46
|
+
outputs: {
|
47
|
+
response: {
|
48
|
+
type: "string",
|
49
|
+
required: true,
|
50
|
+
description: "The generated text"
|
51
|
+
}
|
52
|
+
}
|
53
|
+
)
|
54
|
+
|
55
|
+
provider = CapabilityProvider.new(
|
56
|
+
capability: spec,
|
57
|
+
implementation: lambda do |inputs|
|
58
|
+
# Get the LLM client
|
59
|
+
llm_config = LlmConfig.new
|
60
|
+
llm_config.max_tokens = inputs[:max_tokens] if inputs[:max_tokens]
|
61
|
+
llm_config.temperature = inputs[:temperature] if inputs[:temperature]
|
62
|
+
|
63
|
+
client = Agentic.client(llm_config)
|
64
|
+
|
65
|
+
# Generate text
|
66
|
+
response = client.complete(prompt: inputs[:prompt])
|
67
|
+
|
68
|
+
{response: response.to_s}
|
69
|
+
end
|
70
|
+
)
|
71
|
+
|
72
|
+
registry.register(spec, provider)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Register a web search capability
|
76
|
+
# @return [CapabilitySpecification] The registered capability
|
77
|
+
def register_web_search
|
78
|
+
spec = CapabilitySpecification.new(
|
79
|
+
name: "web_search",
|
80
|
+
description: "Searches the web for information",
|
81
|
+
version: "1.0.0",
|
82
|
+
inputs: {
|
83
|
+
query: {
|
84
|
+
type: "string",
|
85
|
+
required: true,
|
86
|
+
description: "The search query"
|
87
|
+
},
|
88
|
+
num_results: {
|
89
|
+
type: "integer",
|
90
|
+
description: "Number of results to return"
|
91
|
+
}
|
92
|
+
},
|
93
|
+
outputs: {
|
94
|
+
results: {
|
95
|
+
type: "array",
|
96
|
+
required: true,
|
97
|
+
description: "The search results"
|
98
|
+
},
|
99
|
+
sources: {
|
100
|
+
type: "array",
|
101
|
+
description: "The sources of the results"
|
102
|
+
}
|
103
|
+
}
|
104
|
+
)
|
105
|
+
|
106
|
+
provider = CapabilityProvider.new(
|
107
|
+
capability: spec,
|
108
|
+
implementation: lambda do |inputs|
|
109
|
+
# This is a mock implementation
|
110
|
+
# In a real implementation, you would use a search API or web scraping
|
111
|
+
|
112
|
+
query = inputs[:query]
|
113
|
+
num_results = inputs[:num_results] || 3
|
114
|
+
|
115
|
+
results = num_results.times.map do |i|
|
116
|
+
"Result #{i + 1} for query: #{query}"
|
117
|
+
end
|
118
|
+
|
119
|
+
sources = num_results.times.map do |i|
|
120
|
+
"https://example.com/result#{i + 1}"
|
121
|
+
end
|
122
|
+
|
123
|
+
{
|
124
|
+
results: results,
|
125
|
+
sources: sources
|
126
|
+
}
|
127
|
+
end
|
128
|
+
)
|
129
|
+
|
130
|
+
registry.register(spec, provider)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Register a data analysis capability
|
134
|
+
# @return [CapabilitySpecification] The registered capability
|
135
|
+
def register_data_analysis
|
136
|
+
spec = CapabilitySpecification.new(
|
137
|
+
name: "data_analysis",
|
138
|
+
description: "Analyzes data and extracts insights",
|
139
|
+
version: "1.0.0",
|
140
|
+
inputs: {
|
141
|
+
data: {
|
142
|
+
type: "object",
|
143
|
+
required: true,
|
144
|
+
description: "The data to analyze"
|
145
|
+
},
|
146
|
+
analysis_type: {
|
147
|
+
type: "string",
|
148
|
+
description: "The type of analysis to perform"
|
149
|
+
}
|
150
|
+
},
|
151
|
+
outputs: {
|
152
|
+
insights: {
|
153
|
+
type: "array",
|
154
|
+
required: true,
|
155
|
+
description: "The extracted insights"
|
156
|
+
},
|
157
|
+
summary: {
|
158
|
+
type: "string",
|
159
|
+
required: true,
|
160
|
+
description: "A summary of the analysis"
|
161
|
+
}
|
162
|
+
},
|
163
|
+
dependencies: [
|
164
|
+
{name: "text_generation", version: "1.0.0"}
|
165
|
+
]
|
166
|
+
)
|
167
|
+
|
168
|
+
provider = CapabilityProvider.new(
|
169
|
+
capability: spec,
|
170
|
+
implementation: lambda do |inputs|
|
171
|
+
data = inputs[:data]
|
172
|
+
analysis_type = inputs[:analysis_type] || "basic"
|
173
|
+
|
174
|
+
# Get the text generation capability
|
175
|
+
text_gen_provider = registry.get_provider("text_generation")
|
176
|
+
|
177
|
+
# Generate insights based on the data
|
178
|
+
insights_prompt = "Analyze the following data using #{analysis_type} analysis and provide key insights:\n\n#{data.inspect}"
|
179
|
+
insights_response = text_gen_provider.execute(prompt: insights_prompt)[:response]
|
180
|
+
|
181
|
+
# Parse insights
|
182
|
+
insights = insights_response.split("\n").map(&:strip).reject(&:empty?)
|
183
|
+
|
184
|
+
# Generate summary
|
185
|
+
summary_prompt = "Summarize the following insights in one paragraph:\n\n#{insights.join("\n")}"
|
186
|
+
summary = text_gen_provider.execute(prompt: summary_prompt)[:response]
|
187
|
+
|
188
|
+
{
|
189
|
+
insights: insights,
|
190
|
+
summary: summary
|
191
|
+
}
|
192
|
+
end
|
193
|
+
)
|
194
|
+
|
195
|
+
registry.register(spec, provider)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Register a code generation capability
|
199
|
+
# @return [CapabilitySpecification] The registered capability
|
200
|
+
def register_code_generation
|
201
|
+
spec = CapabilitySpecification.new(
|
202
|
+
name: "code_generation",
|
203
|
+
description: "Generates code based on requirements",
|
204
|
+
version: "1.0.0",
|
205
|
+
inputs: {
|
206
|
+
requirements: {
|
207
|
+
type: "string",
|
208
|
+
required: true,
|
209
|
+
description: "The code requirements"
|
210
|
+
},
|
211
|
+
language: {
|
212
|
+
type: "string",
|
213
|
+
required: true,
|
214
|
+
description: "The programming language"
|
215
|
+
},
|
216
|
+
include_comments: {
|
217
|
+
type: "boolean",
|
218
|
+
description: "Whether to include comments in the code"
|
219
|
+
}
|
220
|
+
},
|
221
|
+
outputs: {
|
222
|
+
code: {
|
223
|
+
type: "string",
|
224
|
+
required: true,
|
225
|
+
description: "The generated code"
|
226
|
+
},
|
227
|
+
explanation: {
|
228
|
+
type: "string",
|
229
|
+
description: "Explanation of the code"
|
230
|
+
}
|
231
|
+
},
|
232
|
+
dependencies: [
|
233
|
+
{name: "text_generation", version: "1.0.0"}
|
234
|
+
]
|
235
|
+
)
|
236
|
+
|
237
|
+
provider = CapabilityProvider.new(
|
238
|
+
capability: spec,
|
239
|
+
implementation: lambda do |inputs|
|
240
|
+
requirements = inputs[:requirements]
|
241
|
+
language = inputs[:language]
|
242
|
+
include_comments = inputs[:include_comments] || false
|
243
|
+
|
244
|
+
# Get the text generation capability
|
245
|
+
text_gen_provider = registry.get_provider("text_generation")
|
246
|
+
|
247
|
+
# Generate code
|
248
|
+
comments_instruction = include_comments ? "Include detailed comments." : "Keep comments minimal."
|
249
|
+
code_prompt = "Generate #{language} code for the following requirements:\n\n#{requirements}\n\n#{comments_instruction}"
|
250
|
+
code = text_gen_provider.execute(prompt: code_prompt)[:response]
|
251
|
+
|
252
|
+
# Generate explanation if needed
|
253
|
+
explanation = nil
|
254
|
+
if include_comments
|
255
|
+
explanation_prompt = "Explain the following #{language} code:\n\n#{code}"
|
256
|
+
explanation = text_gen_provider.execute(prompt: explanation_prompt)[:response]
|
257
|
+
end
|
258
|
+
|
259
|
+
result = {code: code}
|
260
|
+
result[:explanation] = explanation if explanation
|
261
|
+
|
262
|
+
result
|
263
|
+
end
|
264
|
+
)
|
265
|
+
|
266
|
+
registry.register(spec, provider)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Register a summarization capability
|
270
|
+
# @return [CapabilitySpecification] The registered capability
|
271
|
+
def register_summarization
|
272
|
+
spec = CapabilitySpecification.new(
|
273
|
+
name: "summarization",
|
274
|
+
description: "Summarizes text content",
|
275
|
+
version: "1.0.0",
|
276
|
+
inputs: {
|
277
|
+
content: {
|
278
|
+
type: "string",
|
279
|
+
required: true,
|
280
|
+
description: "The content to summarize"
|
281
|
+
},
|
282
|
+
max_length: {
|
283
|
+
type: "integer",
|
284
|
+
description: "Maximum length of the summary"
|
285
|
+
},
|
286
|
+
format: {
|
287
|
+
type: "string",
|
288
|
+
description: "Format of the summary (paragraph, bullets, etc.)"
|
289
|
+
}
|
290
|
+
},
|
291
|
+
outputs: {
|
292
|
+
summary: {
|
293
|
+
type: "string",
|
294
|
+
required: true,
|
295
|
+
description: "The generated summary"
|
296
|
+
},
|
297
|
+
key_points: {
|
298
|
+
type: "array",
|
299
|
+
description: "Key points from the content"
|
300
|
+
}
|
301
|
+
},
|
302
|
+
dependencies: [
|
303
|
+
{name: "text_generation", version: "1.0.0"}
|
304
|
+
]
|
305
|
+
)
|
306
|
+
|
307
|
+
provider = CapabilityProvider.new(
|
308
|
+
capability: spec,
|
309
|
+
implementation: lambda do |inputs|
|
310
|
+
content = inputs[:content]
|
311
|
+
max_length = inputs[:max_length]
|
312
|
+
format = inputs[:format] || "paragraph"
|
313
|
+
|
314
|
+
# Get the text generation capability
|
315
|
+
text_gen_provider = registry.get_provider("text_generation")
|
316
|
+
|
317
|
+
# Generate summary
|
318
|
+
length_instruction = max_length ? "Keep the summary under #{max_length} words." : ""
|
319
|
+
summary_prompt = "Summarize the following content in #{format} format. #{length_instruction}\n\n#{content}"
|
320
|
+
summary = text_gen_provider.execute(prompt: summary_prompt)[:response]
|
321
|
+
|
322
|
+
# Extract key points
|
323
|
+
key_points_prompt = "Extract 3-5 key points from the following content:\n\n#{content}"
|
324
|
+
key_points_response = text_gen_provider.execute(prompt: key_points_prompt)[:response]
|
325
|
+
key_points = key_points_response.split("\n").map(&:strip).reject(&:empty?)
|
326
|
+
|
327
|
+
{
|
328
|
+
summary: summary,
|
329
|
+
key_points: key_points
|
330
|
+
}
|
331
|
+
end
|
332
|
+
)
|
333
|
+
|
334
|
+
registry.register(spec, provider)
|
335
|
+
end
|
336
|
+
|
337
|
+
# Register a brainstorming capability
|
338
|
+
# @return [CapabilitySpecification] The registered capability
|
339
|
+
def register_brainstorming
|
340
|
+
spec = CapabilitySpecification.new(
|
341
|
+
name: "brainstorming",
|
342
|
+
description: "Generates creative ideas for a topic",
|
343
|
+
version: "1.0.0",
|
344
|
+
inputs: {
|
345
|
+
topic: {
|
346
|
+
type: "string",
|
347
|
+
required: true,
|
348
|
+
description: "The topic to brainstorm about"
|
349
|
+
},
|
350
|
+
num_ideas: {
|
351
|
+
type: "integer",
|
352
|
+
description: "Number of ideas to generate"
|
353
|
+
},
|
354
|
+
creativity: {
|
355
|
+
type: "number",
|
356
|
+
description: "Creativity level (0.0-1.0)"
|
357
|
+
}
|
358
|
+
},
|
359
|
+
outputs: {
|
360
|
+
ideas: {
|
361
|
+
type: "array",
|
362
|
+
required: true,
|
363
|
+
description: "The generated ideas"
|
364
|
+
},
|
365
|
+
themes: {
|
366
|
+
type: "array",
|
367
|
+
description: "Common themes across the ideas"
|
368
|
+
}
|
369
|
+
},
|
370
|
+
dependencies: [
|
371
|
+
{name: "text_generation", version: "1.0.0"}
|
372
|
+
]
|
373
|
+
)
|
374
|
+
|
375
|
+
provider = CapabilityProvider.new(
|
376
|
+
capability: spec,
|
377
|
+
implementation: lambda do |inputs|
|
378
|
+
topic = inputs[:topic]
|
379
|
+
num_ideas = inputs[:num_ideas] || 10
|
380
|
+
creativity = inputs[:creativity] || 0.7
|
381
|
+
|
382
|
+
# Get the text generation capability
|
383
|
+
text_gen_provider = registry.get_provider("text_generation")
|
384
|
+
|
385
|
+
# Generate ideas
|
386
|
+
ideas_prompt = "Brainstorm #{num_ideas} creative ideas about: #{topic}"
|
387
|
+
ideas_response = text_gen_provider.execute(
|
388
|
+
prompt: ideas_prompt,
|
389
|
+
temperature: creativity
|
390
|
+
)[:response]
|
391
|
+
|
392
|
+
# Parse ideas
|
393
|
+
ideas = ideas_response.split("\n").map { |line| line.sub(/^\d+\.\s*/, "") }.map(&:strip).reject(&:empty?)
|
394
|
+
|
395
|
+
# Identify themes
|
396
|
+
themes_prompt = "Identify 3-5 common themes in the following ideas:\n\n#{ideas.join("\n")}"
|
397
|
+
themes_response = text_gen_provider.execute(prompt: themes_prompt)[:response]
|
398
|
+
themes = themes_response.split("\n").map(&:strip).reject(&:empty?)
|
399
|
+
|
400
|
+
{
|
401
|
+
ideas: ideas,
|
402
|
+
themes: themes
|
403
|
+
}
|
404
|
+
end
|
405
|
+
)
|
406
|
+
|
407
|
+
registry.register(spec, provider)
|
408
|
+
end
|
409
|
+
|
410
|
+
# Register a structured extraction capability
|
411
|
+
# @return [CapabilitySpecification] The registered capability
|
412
|
+
def register_structured_extraction
|
413
|
+
spec = CapabilitySpecification.new(
|
414
|
+
name: "structured_extraction",
|
415
|
+
description: "Extracts structured data from text",
|
416
|
+
version: "1.0.0",
|
417
|
+
inputs: {
|
418
|
+
content: {
|
419
|
+
type: "string",
|
420
|
+
required: true,
|
421
|
+
description: "The content to extract from"
|
422
|
+
},
|
423
|
+
schema: {
|
424
|
+
type: "object",
|
425
|
+
required: true,
|
426
|
+
description: "The schema to extract"
|
427
|
+
}
|
428
|
+
},
|
429
|
+
outputs: {
|
430
|
+
data: {
|
431
|
+
type: "object",
|
432
|
+
required: true,
|
433
|
+
description: "The extracted data"
|
434
|
+
},
|
435
|
+
confidence: {
|
436
|
+
type: "number",
|
437
|
+
description: "Confidence score for the extraction"
|
438
|
+
}
|
439
|
+
},
|
440
|
+
dependencies: [
|
441
|
+
{name: "text_generation", version: "1.0.0"}
|
442
|
+
]
|
443
|
+
)
|
444
|
+
|
445
|
+
provider = CapabilityProvider.new(
|
446
|
+
capability: spec,
|
447
|
+
implementation: lambda do |inputs|
|
448
|
+
content = inputs[:content]
|
449
|
+
schema = inputs[:schema]
|
450
|
+
|
451
|
+
# Get the text generation capability
|
452
|
+
text_gen_provider = registry.get_provider("text_generation")
|
453
|
+
|
454
|
+
# Create extraction prompt
|
455
|
+
schema_str = JSON.pretty_generate(schema)
|
456
|
+
extraction_prompt = <<-PROMPT
|
457
|
+
Extract structured data from the following content according to this schema:
|
458
|
+
|
459
|
+
#{schema_str}
|
460
|
+
|
461
|
+
Content:
|
462
|
+
#{content}
|
463
|
+
|
464
|
+
Return the data as valid JSON.
|
465
|
+
PROMPT
|
466
|
+
|
467
|
+
# Generate extraction
|
468
|
+
extraction_response = text_gen_provider.execute(prompt: extraction_prompt)[:response]
|
469
|
+
|
470
|
+
# Parse JSON (with error handling)
|
471
|
+
data = nil
|
472
|
+
begin
|
473
|
+
# Clean the response to ensure it's valid JSON
|
474
|
+
json_str = extraction_response.gsub(/```json|```/, "").strip
|
475
|
+
data = JSON.parse(json_str)
|
476
|
+
rescue JSON::ParserError
|
477
|
+
# Fallback extraction for malformed JSON
|
478
|
+
data = extract_fallback(extraction_response)
|
479
|
+
end
|
480
|
+
|
481
|
+
# Calculate confidence based on schema match
|
482
|
+
confidence = calculate_confidence(data, schema)
|
483
|
+
|
484
|
+
{
|
485
|
+
data: data,
|
486
|
+
confidence: confidence
|
487
|
+
}
|
488
|
+
end
|
489
|
+
)
|
490
|
+
|
491
|
+
registry.register(spec, provider)
|
492
|
+
end
|
493
|
+
|
494
|
+
private
|
495
|
+
|
496
|
+
def registry
|
497
|
+
AgentCapabilityRegistry.instance
|
498
|
+
end
|
499
|
+
|
500
|
+
# Fallback extraction for malformed JSON
|
501
|
+
def extract_fallback(text)
|
502
|
+
result = {}
|
503
|
+
|
504
|
+
# Try to extract key-value pairs
|
505
|
+
text.scan(/["']?([^"':]+)["']?\s*:\s*["']?([^"',}]+)["']?/) do |key, value|
|
506
|
+
result[key.strip] = value.strip
|
507
|
+
end
|
508
|
+
|
509
|
+
result
|
510
|
+
end
|
511
|
+
|
512
|
+
# Calculate confidence based on schema match
|
513
|
+
def calculate_confidence(data, schema)
|
514
|
+
return 0.0 if data.nil?
|
515
|
+
|
516
|
+
# Count matching fields
|
517
|
+
matches = 0
|
518
|
+
total = 0
|
519
|
+
|
520
|
+
schema.each do |key, _|
|
521
|
+
total += 1
|
522
|
+
matches += 1 if data.key?(key.to_s) || data.key?(key.to_sym)
|
523
|
+
end
|
524
|
+
|
525
|
+
(total > 0) ? matches.to_f / total : 0.0
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "capabilities/examples"
|
4
|
+
|
5
|
+
module Agentic
|
6
|
+
# Namespace for capability-related functionality
|
7
|
+
module Capabilities
|
8
|
+
# Register standard capabilities
|
9
|
+
# @return [void]
|
10
|
+
def self.register_standard_capabilities
|
11
|
+
Examples.register_all
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Agentic
|
4
|
+
# Provider for a capability implementation
|
5
|
+
# @attr_reader [CapabilitySpecification] capability The capability specification
|
6
|
+
# @attr_reader [Proc, Class] implementation The implementation of the capability
|
7
|
+
class CapabilityProvider
|
8
|
+
attr_reader :capability, :implementation
|
9
|
+
|
10
|
+
# Initialize a new capability provider
|
11
|
+
# @param capability [CapabilitySpecification] The capability specification
|
12
|
+
# @param implementation [Proc, Class] The implementation of the capability
|
13
|
+
def initialize(capability:, implementation:)
|
14
|
+
@capability = capability
|
15
|
+
@implementation = implementation
|
16
|
+
end
|
17
|
+
|
18
|
+
# Execute the capability
|
19
|
+
# @param inputs [Hash] The inputs for the capability
|
20
|
+
# @return [Hash] The outputs from the capability
|
21
|
+
def execute(inputs = {})
|
22
|
+
# Validate inputs against capability specification
|
23
|
+
validate_inputs!(inputs)
|
24
|
+
|
25
|
+
# Execute the implementation
|
26
|
+
result = case @implementation
|
27
|
+
when Proc
|
28
|
+
@implementation.call(inputs)
|
29
|
+
when Class
|
30
|
+
instance = @implementation.new
|
31
|
+
instance.execute(inputs)
|
32
|
+
else
|
33
|
+
raise "Invalid implementation type: #{@implementation.class}"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Validate outputs against capability specification
|
37
|
+
validate_outputs!(result)
|
38
|
+
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def validate_inputs!(inputs)
|
45
|
+
# Skip validation if there are no input specifications
|
46
|
+
return unless @capability.inputs && !@capability.inputs.empty?
|
47
|
+
|
48
|
+
# Check for required inputs
|
49
|
+
@capability.inputs.each do |name, spec|
|
50
|
+
if spec[:required] && !inputs.key?(name.to_sym) && !inputs.key?(name.to_s)
|
51
|
+
raise "Missing required input: #{name}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Validate input types (if specified)
|
56
|
+
inputs.each do |name, value|
|
57
|
+
name_sym = name.to_sym
|
58
|
+
name_str = name.to_s
|
59
|
+
|
60
|
+
# Skip inputs that aren't in the specification
|
61
|
+
next unless @capability.inputs.key?(name_sym) || @capability.inputs.key?(name_str)
|
62
|
+
|
63
|
+
# Get the spec for this input
|
64
|
+
input_spec = @capability.inputs[name_sym] || @capability.inputs[name_str]
|
65
|
+
|
66
|
+
# Skip if no type is specified
|
67
|
+
next unless input_spec[:type]
|
68
|
+
|
69
|
+
# Check type
|
70
|
+
case input_spec[:type]
|
71
|
+
when "string"
|
72
|
+
unless value.is_a?(String)
|
73
|
+
raise "Input #{name} must be a string"
|
74
|
+
end
|
75
|
+
when "number", "integer"
|
76
|
+
unless value.is_a?(Numeric)
|
77
|
+
raise "Input #{name} must be a number"
|
78
|
+
end
|
79
|
+
when "boolean"
|
80
|
+
unless value == true || value == false
|
81
|
+
raise "Input #{name} must be a boolean"
|
82
|
+
end
|
83
|
+
when "array"
|
84
|
+
unless value.is_a?(Array)
|
85
|
+
raise "Input #{name} must be an array"
|
86
|
+
end
|
87
|
+
when "object", "hash"
|
88
|
+
unless value.is_a?(Hash)
|
89
|
+
raise "Input #{name} must be an object/hash"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def validate_outputs!(outputs)
|
96
|
+
# Skip validation if there are no output specifications or the output is nil
|
97
|
+
return unless @capability.outputs && !@capability.outputs.empty? && outputs
|
98
|
+
|
99
|
+
# Check for required outputs
|
100
|
+
@capability.outputs.each do |name, spec|
|
101
|
+
if spec[:required] && !outputs.key?(name.to_sym) && !outputs.key?(name.to_s)
|
102
|
+
raise "Missing required output: #{name}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Validate output types (if specified)
|
107
|
+
outputs.each do |name, value|
|
108
|
+
name_sym = name.to_sym
|
109
|
+
name_str = name.to_s
|
110
|
+
|
111
|
+
# Skip outputs that aren't in the specification
|
112
|
+
next unless @capability.outputs.key?(name_sym) || @capability.outputs.key?(name_str)
|
113
|
+
|
114
|
+
# Get the spec for this output
|
115
|
+
output_spec = @capability.outputs[name_sym] || @capability.outputs[name_str]
|
116
|
+
|
117
|
+
# Skip if no type is specified
|
118
|
+
next unless output_spec[:type]
|
119
|
+
|
120
|
+
# Check type
|
121
|
+
case output_spec[:type]
|
122
|
+
when "string"
|
123
|
+
unless value.is_a?(String)
|
124
|
+
raise "Output #{name} must be a string"
|
125
|
+
end
|
126
|
+
when "number", "integer"
|
127
|
+
unless value.is_a?(Numeric)
|
128
|
+
raise "Output #{name} must be a number"
|
129
|
+
end
|
130
|
+
when "boolean"
|
131
|
+
unless value == true || value == false
|
132
|
+
raise "Output #{name} must be a boolean"
|
133
|
+
end
|
134
|
+
when "array"
|
135
|
+
unless value.is_a?(Array)
|
136
|
+
raise "Output #{name} must be an array"
|
137
|
+
end
|
138
|
+
when "object", "hash"
|
139
|
+
unless value.is_a?(Hash)
|
140
|
+
raise "Output #{name} must be an object/hash"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|