minions-py 0.2.0__py3-none-any.whl

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.
minions/__init__.py ADDED
@@ -0,0 +1,64 @@
1
+ '''
2
+ ******************************************************************************************
3
+ Assembly: minions
4
+ Filename: __init__.py
5
+ Author: Terry D. Eppler
6
+ Created: 09-02-2026
7
+
8
+ Last Modified By: Terry D. Eppler
9
+ Last Modified On: 09-06-2026
10
+ ******************************************************************************************
11
+ <copyright file="__init__.py" company="Terry D. Eppler">
12
+
13
+ __init__.py
14
+ Copyright © 2026 Terry D. Eppler
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ of this software and associated documentation files (the “Software”),
18
+ to deal in the Software without restriction,
19
+ including without limitation the rights to use, copy, modify, merge, publish,
20
+ distribute, sublicense, and/or sell copies of the Software,
21
+ and to permit persons to whom the Software is furnished to do so,
22
+ subject to the following conditions:
23
+
24
+ The above copyright notice and this permission notice shall be included in all
25
+ copies or substantial portions of the Software.
26
+
27
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
28
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
29
+ PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
30
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
31
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
32
+ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33
+
34
+ You can contact me at: terryeppler@gmail.com or eppler.terry@epa.gov
35
+
36
+ </copyright>
37
+ <summary>
38
+ Public package utilities for provider-specific Minions.
39
+ </summary>
40
+ ******************************************************************************************
41
+ '''
42
+ from __future__ import annotations
43
+
44
+
45
+ def throw_if( name: str, value: object ) -> None:
46
+ """Validate a required value.
47
+
48
+ Purpose:
49
+ Raises a consistent error when a required value is empty.
50
+
51
+ Args:
52
+ name (str): Argument name included in the error.
53
+ value (object): Value to validate.
54
+
55
+ Returns:
56
+ None: Validation succeeds without returning a value.
57
+ """
58
+ if not value:
59
+ raise ValueError( f'Argument "{name}" cannot be empty!' )
60
+
61
+
62
+ __version__ = '0.2.0'
63
+
64
+ __all__: list[ str ] = [ 'throw_if' ]
minions/claude.py ADDED
@@ -0,0 +1,286 @@
1
+ '''
2
+ ******************************************************************************************
3
+ Assembly: minions
4
+ Filename: claude.py
5
+ Author: Terry D. Eppler
6
+ Created: 09-05-2026
7
+
8
+ Last Modified By: Terry D. Eppler
9
+ Last Modified On: 09-06-2026
10
+ ******************************************************************************************
11
+ <copyright file="claude.py" company="Terry D. Eppler">
12
+
13
+ claude.py
14
+ Copyright © 2026 Terry D. Eppler
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ of this software and associated documentation files (the “Software”),
18
+ to deal in the Software without restriction,
19
+ including without limitation the rights to use, copy, modify, merge, publish,
20
+ distribute, sublicense, and/or sell copies of the Software,
21
+ and to permit persons to whom the Software is furnished to do so,
22
+ subject to the following conditions:
23
+
24
+ The above copyright notice and this permission notice shall be included in all
25
+ copies or substantial portions of the Software.
26
+
27
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
28
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
29
+ PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
30
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
31
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
32
+ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33
+
34
+ You can contact me at: terryeppler@gmail.com or eppler.terry@epa.gov
35
+
36
+ </copyright>
37
+ <summary>
38
+ Anthropic Claude Minion implementations.
39
+ </summary>
40
+ ******************************************************************************************
41
+ '''
42
+ from __future__ import annotations
43
+
44
+ from collections.abc import Sequence
45
+ import asyncio
46
+ import os
47
+
48
+ from anthropic import Anthropic, AsyncAnthropic, beta_async_tool
49
+ from anthropic.lib.tools import (
50
+ BetaAsyncFunctionTool,
51
+ BetaAsyncStreamingToolRunner,
52
+ BetaFunctionTool,
53
+ )
54
+ from anthropic.types.beta import (
55
+ BetaCodeExecutionTool20260521Param,
56
+ BetaMessage,
57
+ BetaToolUnionParam,
58
+ BetaWebFetchTool20260318Param,
59
+ BetaWebSearchTool20260318Param,
60
+ )
61
+
62
+ from . import throw_if
63
+
64
+
65
+ ClaudeTool = BetaFunctionTool | BetaToolUnionParam
66
+ ClaudeAsyncTool = BetaAsyncFunctionTool | BetaToolUnionParam
67
+
68
+
69
+ class Minion:
70
+ """Claude workflow agent backed by Anthropic's native tool runner."""
71
+
72
+ minion_name: str = 'Claude Minion'
73
+
74
+ def __init__( self, model: str, instructions: str,
75
+ tools: Sequence[ ClaudeTool ] | None=None, max_turns: int=10,
76
+ max_tokens: int=4096, api_key: str | None=None,
77
+ name: str | None=None ) -> None:
78
+ """Initialize a Claude Minion.
79
+
80
+ Args:
81
+ model (str): Claude model identifier.
82
+ instructions (str): System instructions for the agent.
83
+ tools (Sequence[ClaudeTool] | None): Optional Anthropic local or server tools.
84
+ max_turns (int): Maximum model iterations per execution.
85
+ max_tokens (int): Maximum output tokens per model iteration.
86
+ api_key (str | None): Optional Anthropic API key override.
87
+ name (str | None): Optional name overriding the implementation default.
88
+
89
+ Returns:
90
+ None: Configuration and native clients are stored.
91
+ """
92
+ throw_if( 'model', model )
93
+ throw_if( 'instructions', instructions )
94
+ if max_turns < 1:
95
+ raise ValueError( 'Argument "max_turns" must be at least 1!' )
96
+ if max_tokens < 1:
97
+ raise ValueError( 'Argument "max_tokens" must be at least 1!' )
98
+ self.name = name or self.minion_name
99
+ throw_if( 'name', self.name )
100
+ self.model = model
101
+ self.instructions = instructions
102
+ self.tools = list( tools or [ ] )
103
+ self.max_turns = max_turns
104
+ self.max_tokens = max_tokens
105
+ self.api_key = api_key or os.getenv( 'ANTHROPIC_API_KEY' )
106
+ throw_if( 'api_key', self.api_key )
107
+ self.async_tools: list[ ClaudeAsyncTool ] = [
108
+ self.create_async_tool( tool ) if isinstance( tool, BetaFunctionTool ) else tool
109
+ for tool in self.tools
110
+ ]
111
+ self.client = Anthropic( api_key=self.api_key )
112
+ self.async_client = AsyncAnthropic( api_key=self.api_key )
113
+ self.result: BetaMessage | BetaAsyncStreamingToolRunner[ object ] | None = None
114
+
115
+
116
+ def create_async_tool( self, tool: BetaFunctionTool ) -> BetaAsyncFunctionTool:
117
+ """Create an async adapter without changing the provider schema.
118
+
119
+ Args:
120
+ tool (BetaFunctionTool): Synchronous Anthropic beta tool.
121
+
122
+ Returns:
123
+ BetaAsyncFunctionTool: Schema-identical asynchronous tool.
124
+ """
125
+ async def invoke( **arguments: object ) -> object:
126
+ return await asyncio.to_thread( tool.func, **arguments )
127
+
128
+ invoke.__name__ = tool.name
129
+ invoke.__doc__ = tool.description
130
+ return beta_async_tool( invoke, name=tool.name, description=tool.description,
131
+ input_schema=tool.input_schema, )
132
+
133
+
134
+ def run( self, prompt: str ) -> BetaMessage:
135
+ """Execute the complete Claude workflow synchronously.
136
+
137
+ Args:
138
+ prompt (str): User input for the workflow.
139
+
140
+ Returns:
141
+ BetaMessage: Final provider-native message.
142
+ """
143
+ throw_if( 'prompt', prompt )
144
+ runner = self.client.beta.messages.tool_runner( model=self.model,
145
+ max_tokens=self.max_tokens, max_iterations=self.max_turns, system=self.instructions,
146
+ tools=self.tools, messages=[ { 'role': 'user', 'content': prompt } ], )
147
+ result = runner.until_done( )
148
+ self.result = result
149
+ return self.result
150
+
151
+
152
+ async def run_async( self, prompt: str ) -> BetaMessage:
153
+ """Execute the complete Claude workflow asynchronously.
154
+
155
+ Args:
156
+ prompt (str): User input for the workflow.
157
+
158
+ Returns:
159
+ BetaMessage: Final provider-native message.
160
+ """
161
+ throw_if( 'prompt', prompt )
162
+ runner = self.async_client.beta.messages.tool_runner(
163
+ model=self.model,
164
+ max_tokens=self.max_tokens,
165
+ max_iterations=self.max_turns,
166
+ system=self.instructions,
167
+ tools=self.async_tools,
168
+ messages=[ { 'role': 'user', 'content': prompt } ],
169
+ )
170
+ result = await runner.until_done( )
171
+ self.result = result
172
+ return result
173
+
174
+
175
+ def stream( self, prompt: str ) -> BetaAsyncStreamingToolRunner[ object ]:
176
+ """Start Anthropic's complete async streaming tool runner.
177
+
178
+ Args:
179
+ prompt (str): User input for the workflow.
180
+
181
+ Returns:
182
+ BetaAsyncStreamingToolRunner[object]: Native runner that streams and executes tools.
183
+ """
184
+ throw_if( 'prompt', prompt )
185
+ runner = self.async_client.beta.messages.tool_runner(
186
+ model=self.model,
187
+ max_tokens=self.max_tokens,
188
+ max_iterations=self.max_turns,
189
+ system=self.instructions,
190
+ tools=self.async_tools,
191
+ messages=[ { 'role': 'user', 'content': prompt } ],
192
+ stream=True,
193
+ )
194
+ self.result = runner
195
+ return runner
196
+
197
+
198
+ class DataMinion( Minion ):
199
+ """Claude Minion specialized for data workflows."""
200
+
201
+ minion_name: str = 'Data Minion'
202
+
203
+
204
+ class GovernanceMinion( Minion ):
205
+ """Claude Minion specialized for governance workflows."""
206
+
207
+ minion_name: str = 'Governance Minion'
208
+
209
+
210
+ class ResearchMinion( Minion ):
211
+ """Claude Minion specialized for research workflows."""
212
+
213
+ minion_name: str = 'Research Minion'
214
+
215
+
216
+ class CodingMinion( Minion ):
217
+ """Claude Minion specialized for software workflows."""
218
+
219
+ minion_name: str = 'Coding Minion'
220
+
221
+
222
+ class WritingMinion( Minion ):
223
+ """Claude Minion specialized for writing workflows."""
224
+
225
+ minion_name: str = 'Writing Minion'
226
+
227
+
228
+ class PlanningMinion( Minion ):
229
+ """Claude Minion specialized for planning workflows."""
230
+
231
+ minion_name: str = 'Planning Minion'
232
+
233
+
234
+ class ComplianceMinion( Minion ):
235
+ """Claude Minion specialized for compliance, legal, and budget workflows."""
236
+
237
+ minion_name: str = 'Compliance Minion'
238
+
239
+
240
+ class BusinessMinion( Minion ):
241
+ """Claude Minion specialized for business, finance, and marketing workflows."""
242
+
243
+ minion_name: str = 'Business Minion'
244
+
245
+
246
+ class ImageGenerationMinion( Minion ):
247
+ """Claude Minion specialized for image-generation workflows."""
248
+
249
+ minion_name: str = 'Image Generation Minion'
250
+
251
+
252
+ class ImageAnalysisMinion( Minion ):
253
+ """Claude Minion specialized for image-analysis workflows."""
254
+
255
+ minion_name: str = 'Image Analysis Minion'
256
+
257
+
258
+ class ImageEditingMinion( Minion ):
259
+ """Claude Minion specialized for image-editing workflows."""
260
+
261
+ minion_name: str = 'Image Editing Minion'
262
+
263
+
264
+ class TranslationMinion( Minion ):
265
+ """Claude Minion specialized for translation workflows."""
266
+
267
+ minion_name: str = 'Translation Minion'
268
+
269
+
270
+ class TranscriptionMinion( Minion ):
271
+ """Claude Minion specialized for transcription workflows."""
272
+
273
+ minion_name: str = 'Transcription Minion'
274
+
275
+
276
+ class SpeechMinion( Minion ):
277
+ """Claude Minion specialized for speech workflows."""
278
+
279
+ minion_name: str = 'Speech Minion'
280
+
281
+ __all__: list[ str ] = [ 'BetaCodeExecutionTool20260521Param',
282
+ 'BetaWebFetchTool20260318Param', 'BetaWebSearchTool20260318Param', 'BusinessMinion',
283
+ 'ClaudeAsyncTool', 'ClaudeTool', 'CodingMinion', 'ComplianceMinion', 'DataMinion',
284
+ 'GovernanceMinion', 'ImageAnalysisMinion', 'ImageEditingMinion',
285
+ 'ImageGenerationMinion', 'Minion', 'PlanningMinion', 'ResearchMinion', 'SpeechMinion',
286
+ 'TranscriptionMinion', 'TranslationMinion', 'WritingMinion', ]
minions/config.py ADDED
@@ -0,0 +1,211 @@
1
+ '''
2
+ ******************************************************************************************
3
+ Assembly: minions
4
+ Filename: config.py
5
+ Author: Terry D. Eppler
6
+ Created: 09-03-2026
7
+
8
+ Last Modified By: Terry D. Eppler
9
+ Last Modified On: 09-06-2026
10
+ ******************************************************************************************
11
+ <copyright file="config.py" company="Terry D. Eppler">
12
+
13
+ config.py
14
+ Copyright © 2026 Terry D. Eppler
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ of this software and associated documentation files (the “Software”),
18
+ to deal in the Software without restriction,
19
+ including without limitation the rights to use, copy, modify, merge, publish,
20
+ distribute, sublicense, and/or sell copies of the Software,
21
+ and to permit persons to whom the Software is furnished to do so,
22
+ subject to the following conditions:
23
+
24
+ The above copyright notice and this permission notice shall be included in all
25
+ copies or substantial portions of the Software.
26
+
27
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
28
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
29
+ PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
30
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
31
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
32
+ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33
+
34
+ You can contact me at: terryeppler@gmail.com or eppler.terry@epa.gov
35
+
36
+ </copyright>
37
+ <summary>
38
+ Environment and model configuration for Minions.
39
+ </summary>
40
+ ******************************************************************************************
41
+ '''
42
+
43
+ import os
44
+ from pathlib import Path
45
+
46
+ # -------------- APP-LEVEL UTILITIES -------------
47
+
48
+ def throw_if( name: str, value: object ) -> None:
49
+ """Raise a ``ValueError`` when a required value is empty.
50
+
51
+ Purpose:
52
+ Provides a small, consistent guard for required arguments and configuration values. The
53
+ function treats falsy values as invalid and raises a ``ValueError`` containing the
54
+ caller-supplied argument or setting name.
55
+
56
+ Args:
57
+ name (str): Name of the argument or configuration value being validated.
58
+ value (object): Value to validate.
59
+
60
+ Raises:
61
+ ValueError: Raised when ``value`` is falsy.
62
+ """
63
+ if not value:
64
+ raise ValueError( f'Argument "{name}" cannot be empty!' )
65
+
66
+ def get_bool( name: str, default: bool = False ) -> bool:
67
+ """Read a Boolean environment variable.
68
+
69
+ Purpose:
70
+ Converts environment-variable text into a deterministic Boolean value. Missing
71
+ variables return the caller-provided default. Values of ``1``, ``true``, ``yes``,
72
+ ``y``, and ``on`` are treated as ``True``; all other defined values are treated as
73
+ ``False``.
74
+
75
+ Args:
76
+ name (str): Environment variable name.
77
+ default (bool): Default value used when the environment variable is not defined.
78
+
79
+ Returns:
80
+ Parsed Boolean value, or the original default value when parsing fails.
81
+ """
82
+ try:
83
+ throw_if( 'name', name )
84
+ value = os.getenv( name )
85
+ return default if value is None else value.strip( ).lower( ) in (
86
+ '1',
87
+ 'true',
88
+ 'yes',
89
+ 'y',
90
+ 'on'
91
+ )
92
+ except Exception:
93
+ return default
94
+
95
+ def get_int( name: str, default: int ) -> int:
96
+ """Read an integer environment variable.
97
+
98
+ Purpose:
99
+ Parses an optional environment variable as an integer while preserving a safe
100
+ default when the variable is missing, empty, or invalid. This keeps module import
101
+ safe even when deployment configuration is incomplete.
102
+
103
+ Args:
104
+ name (str): Environment variable name.
105
+ default (int): Default integer value used when parsing is not possible.
106
+
107
+ Returns:
108
+ Parsed integer value or the supplied default value.
109
+ """
110
+ try:
111
+ throw_if( 'name', name )
112
+ value = os.getenv( name )
113
+ return default if value in (None, '') else int( str( value ).strip( ) )
114
+ except Exception:
115
+ return default
116
+
117
+ def get_float( name: str, default: float ) -> float:
118
+ """Read a floating-point environment variable.
119
+
120
+ Purpose:
121
+ Parses an optional environment variable as a float while preserving a safe default
122
+ when the variable is missing, empty, or invalid. This helper supports numeric
123
+ configuration without making module import dependent on perfect environment state.
124
+
125
+ Args:
126
+ name (str): Environment variable name.
127
+ default (float): Default floating-point value used when parsing is not possible.
128
+
129
+ Returns:
130
+ Parsed floating-point value or the supplied default value.
131
+ """
132
+ try:
133
+ throw_if( 'name', name )
134
+ value = os.getenv( name )
135
+ return default if value in (None, '') else float( str( value ).strip( ) )
136
+ except Exception:
137
+ return default
138
+
139
+ def get_path( name: str, default: Path ) -> Path:
140
+ """Read a path environment variable.
141
+
142
+ Purpose:
143
+ Resolves optional filesystem configuration from the environment. Missing variables
144
+ return the resolved default path, and invalid values fall back to the resolved
145
+ default path rather than interrupting module import.
146
+
147
+ Args:
148
+ name (str): Environment variable name.
149
+ default (Path): Default path used when the environment variable is not defined.
150
+
151
+ Returns:
152
+ Resolved path value or the resolved default path.
153
+ """
154
+ try:
155
+ throw_if( 'name', name )
156
+ throw_if( 'default', default )
157
+ value = os.getenv( name )
158
+ return Path( value ).resolve( ) if value else default.resolve( )
159
+ except Exception:
160
+ return default.resolve( )
161
+
162
+ def get_text( name: str, default: str ) -> str:
163
+ """Read a text environment variable.
164
+
165
+ Purpose:
166
+ Returns an environment variable as text while preserving the supplied default when
167
+ the variable is missing or empty. This keeps optional configuration centralized and
168
+ stable for callers that import the module early in application startup.
169
+
170
+ Args:
171
+ name (str): Environment variable name.
172
+ default (str): Default text value.
173
+
174
+ Returns:
175
+ Environment value or supplied default.
176
+ """
177
+ try:
178
+ throw_if( 'name', name )
179
+ value = os.getenv( name )
180
+ return default if value in (None, '') else str( value )
181
+ except Exception:
182
+ return default
183
+
184
+ # ------ CONSTANTS -------------------
185
+
186
+ BASE_DIR = Path( __file__ ).resolve( ).parent
187
+ ROOT_DIR = Path( __file__ ).resolve( ).parent
188
+ LOG_DIR: Path = get_path( 'LOG_DIR', ROOT_DIR / 'logging' )
189
+ LOG_PATH: str = get_text( 'LOG_PATH', str( LOG_DIR / 'Exceptions.db' ) )
190
+ LOG_FILE: str = get_text( 'LOG_FILE', 'Exceptions' )
191
+
192
+ # ------ MODELS -------------------
193
+
194
+ GPT_MODELS = [ 'gpt-5.6-sol', 'gpt-5.6-luna', 'gpt-5-mini', 'gpt-5-nano', 'gpt-4.1',
195
+ 'gpt-4.1-mini', ]
196
+
197
+ GEMINI_MODELS = [ 'gemini-3.7-flash', 'gemini-3.6-flash', 'gemini-3.5-flash',
198
+ 'gemini-3.5-flash-lite', 'gemini-3.1-pro-preview', 'gemini-3.1-flash-lite',
199
+ 'gemini-3-flash-preview', 'gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite', ]
200
+
201
+ GROK_MODELS = [ 'grok-4.6', 'grok-4.5', 'grok-4.3', 'grok-4.20-multi-agent',
202
+ 'grok-4.20-multi-agent-latest', ]
203
+
204
+ CLAUDE_MODELS = [ 'claude-fable-5', 'claude-opus-5', 'claude-opus-4-8', 'claude-opus-4-7',
205
+ 'claude-opus-4-6', 'claude-sonnet-5', 'claude-sonnet-4-6', 'claude-sonnet-4-5-20250929',
206
+ 'claude-haiku-4-5-20251001', ]
207
+
208
+ MISTRAL_MODELS = [ 'mistral-medium-latest', 'mistral-large-latest',
209
+ 'mistral-small-latest', 'codestral-latest',
210
+ 'ministral-14b-latest', 'ministral-8b-latest',
211
+ 'ministral-3b-latest', ]