minion-ai 0.1.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.
- minion_ai-0.1.0/.claude/settings.local.json +11 -0
- minion_ai-0.1.0/.env +2 -0
- minion_ai-0.1.0/PKG-INFO +14 -0
- minion_ai-0.1.0/README.md +2 -0
- minion_ai-0.1.0/minions/TODO.md +77 -0
- minion_ai-0.1.0/minions/__init__.py +4 -0
- minion_ai-0.1.0/minions/client.py +16 -0
- minion_ai-0.1.0/minions/config.py +37 -0
- minion_ai-0.1.0/minions/minion.py +204 -0
- minion_ai-0.1.0/minions/models.py +34 -0
- minion_ai-0.1.0/minions/tools.py +49 -0
- minion_ai-0.1.0/pyproject.toml +23 -0
- minion_ai-0.1.0/requirements.txt +4 -0
- minion_ai-0.1.0/test.ipynb +105 -0
minion_ai-0.1.0/.env
ADDED
minion_ai-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: minion-ai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A simple agentic framework with observability and evals baked in
|
|
5
|
+
Project-URL: Repository, https://github.com/shriyansnaik/minions
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: docstring-parser
|
|
8
|
+
Requires-Dist: litellm
|
|
9
|
+
Requires-Dist: openai
|
|
10
|
+
Requires-Dist: pydantic
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# minions
|
|
14
|
+
A simple agentic framework - observability, evals baked in
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Minions — TODO
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 1. Provider Agnostic (LiteLLM)
|
|
6
|
+
|
|
7
|
+
**Problem:** Right now the code calls `client.responses.parse(...)` which is OpenAI's Responses API.
|
|
8
|
+
Other providers (Anthropic, Gemini, Mistral, etc.) do not have this API, so swapping the provider is not just swapping the key — it requires changing the call itself.
|
|
9
|
+
|
|
10
|
+
**Solution:** Use [LiteLLM](https://github.com/BerriAI/litellm) as an abstraction layer.
|
|
11
|
+
LiteLLM wraps every major provider behind one unified interface.
|
|
12
|
+
|
|
13
|
+
**What needs to change:**
|
|
14
|
+
- Replace `client.responses.parse(...)` with a LiteLLM call
|
|
15
|
+
- LiteLLM has structured output support via `response_format` — use that instead of `text_format`
|
|
16
|
+
- `minions.init()` would accept `provider` or just let the model string carry it (LiteLLM style: `"anthropic/claude-opus-4"`, `"gemini/gemini-2.5-pro"`)
|
|
17
|
+
- Drop the custom `get_client()` — LiteLLM handles the client internally
|
|
18
|
+
|
|
19
|
+
**New init signature:**
|
|
20
|
+
```python
|
|
21
|
+
minions.init(api_key="...", model_prefix="anthropic")
|
|
22
|
+
# or just pass model strings like "anthropic/claude-opus-4" to Minion directly
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Observability UI (minions-ui)
|
|
28
|
+
|
|
29
|
+
A separate open source Docker app that shows traces from all Minion runs.
|
|
30
|
+
|
|
31
|
+
### Architecture
|
|
32
|
+
```
|
|
33
|
+
FastAPI backend ← Minion posts traces here after each run
|
|
34
|
+
SQLite database ← FastAPI stores traces as JSON
|
|
35
|
+
React frontend ← reads from FastAPI, renders trace tree
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### How users run it
|
|
39
|
+
```bash
|
|
40
|
+
git clone https://github.com/you/minions-ui
|
|
41
|
+
cd minions-ui
|
|
42
|
+
docker compose up
|
|
43
|
+
# UI available at http://localhost:7337
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### How users enable tracing in their code
|
|
47
|
+
```python
|
|
48
|
+
import minions
|
|
49
|
+
minions.init(api_key="...", tracing=True, ui_url="http://localhost:7337")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### What the UI shows
|
|
53
|
+
- List of all runs (left panel) — timestamp, model, first user message
|
|
54
|
+
- Trace tree (right panel) for selected run:
|
|
55
|
+
- User input
|
|
56
|
+
- Each turn: thought → tool calls (with args) → tool outputs
|
|
57
|
+
- Sub-minion runs nested under the parent
|
|
58
|
+
- Final answer
|
|
59
|
+
- Token usage + latency per turn
|
|
60
|
+
|
|
61
|
+
### What needs to be built
|
|
62
|
+
- [ ] `Minion.to_trace()` — serializes conversation + raw_model_responses to a JSON blob
|
|
63
|
+
- [ ] POST to `ui_url/api/traces` after `_finish` is called
|
|
64
|
+
- [ ] FastAPI app with two endpoints: `POST /api/traces`, `GET /api/traces`, `GET /api/traces/{id}`
|
|
65
|
+
- [ ] SQLite schema: `id`, `timestamp`, `model`, `input`, `trace_json`
|
|
66
|
+
- [ ] React app (Vite + React, no UI kit) with run list + trace tree
|
|
67
|
+
- [ ] Dockerfile + docker-compose.yml
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 3. Package Setup (pip installable)
|
|
72
|
+
|
|
73
|
+
- [ ] Add `pyproject.toml` (or `setup.py`)
|
|
74
|
+
- [ ] Decide package name (e.g. `minions-ai`)
|
|
75
|
+
- [ ] Publish to PyPI
|
|
76
|
+
- [ ] `minions[ui]` optional dependency group for FastAPI + uvicorn
|
|
77
|
+
- [ ] `minions ui` CLI command to start the UI server locally without Docker
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from openai import OpenAI
|
|
2
|
+
from .config import get_config
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_client() -> OpenAI:
|
|
6
|
+
config = get_config()
|
|
7
|
+
|
|
8
|
+
if not config.api_key:
|
|
9
|
+
raise RuntimeError(
|
|
10
|
+
"No API key set. Call minions.init(api_key='...') before using Minion."
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
return OpenAI(
|
|
14
|
+
api_key=config.api_key,
|
|
15
|
+
base_url=config.base_url,
|
|
16
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class _Config:
|
|
7
|
+
api_key: Optional[str] = None
|
|
8
|
+
base_url: Optional[str] = None
|
|
9
|
+
tracing: bool = False
|
|
10
|
+
ui_url: Optional[str] = None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_config = _Config()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def init(
|
|
17
|
+
api_key: str,
|
|
18
|
+
base_url: str = None,
|
|
19
|
+
tracing: bool = False,
|
|
20
|
+
ui_url: str = None,
|
|
21
|
+
):
|
|
22
|
+
"""Configure the minions library. Call once before creating any Minion.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
api_key: Your OpenAI API key.
|
|
26
|
+
base_url: Optional custom endpoint (Azure, vLLM, any OpenAI-compatible API).
|
|
27
|
+
tracing: Enable trace collection.
|
|
28
|
+
ui_url: URL of the minions-ui server (required if tracing=True).
|
|
29
|
+
"""
|
|
30
|
+
_config.api_key = api_key
|
|
31
|
+
_config.base_url = base_url
|
|
32
|
+
_config.tracing = tracing
|
|
33
|
+
_config.ui_url = ui_url
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_config() -> _Config:
|
|
37
|
+
return _config
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import docstring_parser
|
|
3
|
+
from typing import Callable, Literal
|
|
4
|
+
|
|
5
|
+
from .models import Tool, ToolArg, ToolCall, MinionOutput
|
|
6
|
+
from .client import get_client
|
|
7
|
+
|
|
8
|
+
MINION_BASE_PROMPT = """#Role
|
|
9
|
+
You are Minion, a powerful AI agent. Given user input, produce the best possible output using your available tools.
|
|
10
|
+
|
|
11
|
+
====================================================
|
|
12
|
+
# Tools You Have
|
|
13
|
+
|
|
14
|
+
{tool_schemas}
|
|
15
|
+
====================================================
|
|
16
|
+
|
|
17
|
+
## Sub Minions (Delegating huge tasks)
|
|
18
|
+
If the above tools include `_spawn_sub_minion`, use it to delegate large tasks.
|
|
19
|
+
|
|
20
|
+
**Trigger rules (apply these before starting work):**
|
|
21
|
+
- Task involves reading or processing **3+ independent files/URLs/items** → delegate to sub minions
|
|
22
|
+
- Split items evenly: e.g. 12 files → 3 sub minions × 4 files each
|
|
23
|
+
- Each sub minion gets: the same instructions + its specific subset of items
|
|
24
|
+
- You synthesize their results; you do NOT read the files yourself
|
|
25
|
+
|
|
26
|
+
Only skip delegation if items are fewer than 3, or one item's output feeds another.
|
|
27
|
+
|
|
28
|
+
## Thoughts
|
|
29
|
+
Keep thoughts concise — enough for the human to follow your reasoning. Do not name tools directly; describe your intent instead.
|
|
30
|
+
|
|
31
|
+
## Tool Arguments
|
|
32
|
+
All tool args must be valid JSON. Escape double quotes where needed.
|
|
33
|
+
|
|
34
|
+
## Multiple Tools
|
|
35
|
+
Call independent tools simultaneously; only sequence tools when one depends on another's output.
|
|
36
|
+
|
|
37
|
+
## Output Format
|
|
38
|
+
Every response must include `next_thought` and `next_tools`.
|
|
39
|
+
When you have sufficient information to answer, call `_finish` with your answer in `final_response`."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Minion:
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
model: str,
|
|
47
|
+
reasoning_effort: str = None,
|
|
48
|
+
secondary_model: str = None,
|
|
49
|
+
secondary_model_reasoning_effort: str = None,
|
|
50
|
+
system_prompt: str = None,
|
|
51
|
+
tools: list | None = [],
|
|
52
|
+
allow_sub_agents: bool = False,
|
|
53
|
+
max_turns: int = 10,
|
|
54
|
+
):
|
|
55
|
+
self.model = model
|
|
56
|
+
self.reasoning_effort = reasoning_effort
|
|
57
|
+
self.secondary_model = secondary_model
|
|
58
|
+
self.secondary_model_reasoning_effort = secondary_model_reasoning_effort
|
|
59
|
+
self.system_prompt = system_prompt
|
|
60
|
+
self.tools = tools
|
|
61
|
+
self.parsed_tools = [self._parse_tool(tool) for tool in tools]
|
|
62
|
+
self.allow_sub_agents = allow_sub_agents
|
|
63
|
+
self.max_turns = max_turns
|
|
64
|
+
|
|
65
|
+
self.raw_model_responses = []
|
|
66
|
+
|
|
67
|
+
if allow_sub_agents:
|
|
68
|
+
if not secondary_model:
|
|
69
|
+
print("No secondary model setup, sub agents will use the main model")
|
|
70
|
+
self.secondary_model = model
|
|
71
|
+
if not secondary_model_reasoning_effort:
|
|
72
|
+
print("No secondary model reasoning settings found, sub agents will use the main models reasoning settings")
|
|
73
|
+
self.secondary_model_reasoning_effort = reasoning_effort
|
|
74
|
+
self.parsed_tools.append(self._parse_tool(self._spawn_sub_minion))
|
|
75
|
+
|
|
76
|
+
self.parsed_tools.append(self._parse_tool(self._finish))
|
|
77
|
+
|
|
78
|
+
self.conversation = []
|
|
79
|
+
self.instructions = None
|
|
80
|
+
|
|
81
|
+
def _parse_tool(self, fn: Callable) -> Tool:
|
|
82
|
+
sig = inspect.signature(fn)
|
|
83
|
+
parsed_doc = docstring_parser.parse(inspect.getdoc(fn) or "")
|
|
84
|
+
param_descriptions = {p.arg_name: p.description for p in parsed_doc.params}
|
|
85
|
+
|
|
86
|
+
parameters = {}
|
|
87
|
+
for name, param in sig.parameters.items():
|
|
88
|
+
annotation = param.annotation
|
|
89
|
+
parameters[name] = {
|
|
90
|
+
"type": annotation.__name__ if annotation != inspect.Parameter.empty else "string",
|
|
91
|
+
"description": param_descriptions.get(name, "")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return Tool(
|
|
95
|
+
schema={
|
|
96
|
+
"tool_name": fn.__name__,
|
|
97
|
+
"description": parsed_doc.short_description or "",
|
|
98
|
+
"parameters": parameters,
|
|
99
|
+
},
|
|
100
|
+
fn=fn,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def _finish(self, final_response: str) -> str:
|
|
104
|
+
"""Call this when you have completed the user's request.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
final_response: The message shown directly to the user. Must be phrased as a direct answer or summary of the completed request.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
The final_response string, passed through unchanged.
|
|
111
|
+
"""
|
|
112
|
+
return final_response
|
|
113
|
+
|
|
114
|
+
def _spawn_sub_minion(self, input: str, tool_list: list[str] = []) -> str:
|
|
115
|
+
"""Spawn an independent sub-minion to complete a task and return its answer.
|
|
116
|
+
|
|
117
|
+
Use when a task is large, separable into parallel subtasks, or requires reading many files (eg. spawn 25 sub-minions each reading 4 files rather than reading 100 yourself — too much information can confuse you if read all at once).
|
|
118
|
+
|
|
119
|
+
The sub-minion has no conversation memory, so `input` must be fully self-contained: include all context, constraints, and instructions needed to complete the task from scratch.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
input: Self-contained task description with all required context.
|
|
123
|
+
tool_list: Restrict to specific tools by passing the names to prevent sub minion to go rogue. If tool_list is not passed, sub minion will have all tools.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The sub-minion's final answer.
|
|
127
|
+
"""
|
|
128
|
+
if not tool_list:
|
|
129
|
+
tools = self.tools
|
|
130
|
+
else:
|
|
131
|
+
tools = [tool.fn for tool in self.parsed_tools if tool.schema["tool_name"] in tool_list]
|
|
132
|
+
|
|
133
|
+
sub_minion = Minion(
|
|
134
|
+
model=self.secondary_model,
|
|
135
|
+
reasoning_effort=self.secondary_model_reasoning_effort,
|
|
136
|
+
tools=tools,
|
|
137
|
+
allow_sub_agents=False,
|
|
138
|
+
)
|
|
139
|
+
print("=== CREATED A SUB MINION. RUNNING IT NOW ===")
|
|
140
|
+
sub_output = sub_minion(input)
|
|
141
|
+
print("=== SUB MINION HAS ENDED ===")
|
|
142
|
+
return sub_output
|
|
143
|
+
|
|
144
|
+
def _add_to_conversation(
|
|
145
|
+
self,
|
|
146
|
+
message: MinionOutput | str,
|
|
147
|
+
message_type: Literal["system", "user", "thought", "tool", "tool_output"] = None,
|
|
148
|
+
):
|
|
149
|
+
if isinstance(message, MinionOutput):
|
|
150
|
+
self.conversation.append({"message_type": "thought", "content": message.next_thought})
|
|
151
|
+
|
|
152
|
+
for tool in message.next_tools:
|
|
153
|
+
args = ", ".join(f"{a.key}={a.value!r}" for a in tool.args)
|
|
154
|
+
self.conversation.append({
|
|
155
|
+
"message_type": "tool",
|
|
156
|
+
"content": f"Tool('{tool.tool_name}' called with Args({args}))",
|
|
157
|
+
})
|
|
158
|
+
else:
|
|
159
|
+
self.conversation.append({
|
|
160
|
+
"message_type": message_type or "tool_output",
|
|
161
|
+
"content": message,
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
def invoke_tool(self, tool_name: str, args: dict) -> str:
|
|
165
|
+
matches = [t for t in self.parsed_tools if t.schema["tool_name"] == tool_name]
|
|
166
|
+
if not matches:
|
|
167
|
+
raise ValueError(f"Tool '{tool_name}' not found")
|
|
168
|
+
return matches[0].fn(**args)
|
|
169
|
+
|
|
170
|
+
def __call__(self, input: str) -> str:
|
|
171
|
+
self._add_to_conversation(message=input, message_type="user")
|
|
172
|
+
|
|
173
|
+
tool_schemas = "\n".join([str(tool.schema) for tool in self.parsed_tools])
|
|
174
|
+
self.instructions = MINION_BASE_PROMPT.format(tool_schemas=tool_schemas)
|
|
175
|
+
if self.system_prompt:
|
|
176
|
+
self.instructions += f"\n\n## Special Instructions from User\n{self.system_prompt}"
|
|
177
|
+
|
|
178
|
+
client = get_client()
|
|
179
|
+
|
|
180
|
+
for _ in range(self.max_turns):
|
|
181
|
+
response = client.responses.parse(
|
|
182
|
+
model=self.model,
|
|
183
|
+
instructions=self.instructions,
|
|
184
|
+
reasoning={"effort": self.reasoning_effort, "summary": "detailed"},
|
|
185
|
+
input=str(self.conversation),
|
|
186
|
+
text_format=MinionOutput,
|
|
187
|
+
)
|
|
188
|
+
self.raw_model_responses.append(response)
|
|
189
|
+
|
|
190
|
+
output = response.output_parsed
|
|
191
|
+
print(output, end="\n\n")
|
|
192
|
+
self._add_to_conversation(message=output)
|
|
193
|
+
|
|
194
|
+
for tool in output.next_tools:
|
|
195
|
+
tool_output = self.invoke_tool(
|
|
196
|
+
tool_name=tool.tool_name,
|
|
197
|
+
args={a.key: a.value for a in tool.args},
|
|
198
|
+
)
|
|
199
|
+
self._add_to_conversation(message=tool_output)
|
|
200
|
+
|
|
201
|
+
if tool.tool_name == "_finish":
|
|
202
|
+
return tool_output
|
|
203
|
+
|
|
204
|
+
print(f"OOPS!! {self.max_turns} turns were not enough")
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Callable
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class Tool:
|
|
8
|
+
fn: Callable
|
|
9
|
+
schema: dict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ToolArg(BaseModel):
|
|
13
|
+
key: str
|
|
14
|
+
value: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ToolCall(BaseModel):
|
|
18
|
+
model_config = {"extra": "forbid"}
|
|
19
|
+
tool_name: str
|
|
20
|
+
args: list[ToolArg]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MinionOutput(BaseModel):
|
|
24
|
+
model_config = {"extra": "forbid"}
|
|
25
|
+
next_thought: str
|
|
26
|
+
next_tools: list[ToolCall]
|
|
27
|
+
|
|
28
|
+
def __str__(self) -> str:
|
|
29
|
+
thought = f"Thought: {self.next_thought!r}"
|
|
30
|
+
tools = "\n".join([
|
|
31
|
+
f"Tool_{i+1}: {tool.tool_name!r}\nArgs: {', '.join(f'{a.key}={a.value!r}' for a in tool.args)}"
|
|
32
|
+
for i, tool in enumerate(self.next_tools)
|
|
33
|
+
])
|
|
34
|
+
return "\n".join([thought, tools])
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def read_file(file_path: str, encoding: str = "utf-8") -> str:
|
|
5
|
+
"""Reads the contents of a file and returns them as a string.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
file_path: Path to the file to be read.
|
|
9
|
+
encoding: Character encoding to use when decoding the file.
|
|
10
|
+
Defaults to 'utf-8'.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
The full contents of the file as a string.
|
|
14
|
+
|
|
15
|
+
Raises:
|
|
16
|
+
FileNotFoundError: If the file does not exist at the given path.
|
|
17
|
+
PermissionError: If the process lacks read access to the file.
|
|
18
|
+
UnicodeDecodeError: If the file cannot be decoded with the given encoding.
|
|
19
|
+
"""
|
|
20
|
+
with open(file_path, "r", encoding=encoding) as f:
|
|
21
|
+
return f.read()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def list_files(directory: str) -> list[str]:
|
|
25
|
+
"""Lists all file paths within a directory, excluding hidden and private entries.
|
|
26
|
+
|
|
27
|
+
Skips files and directories whose names start with '.' or '_'.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
directory: Path to the directory to list files from.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
A list of absolute file paths found in the directory.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
FileNotFoundError: If the directory does not exist.
|
|
37
|
+
NotADirectoryError: If the given path is not a directory.
|
|
38
|
+
"""
|
|
39
|
+
if not os.path.exists(directory):
|
|
40
|
+
raise FileNotFoundError(f"Directory not found: {directory}")
|
|
41
|
+
if not os.path.isdir(directory):
|
|
42
|
+
raise NotADirectoryError(f"Path is not a directory: {directory}")
|
|
43
|
+
|
|
44
|
+
return [
|
|
45
|
+
os.path.join(directory, entry)
|
|
46
|
+
for entry in os.listdir(directory)
|
|
47
|
+
if not entry.startswith((".", "_"))
|
|
48
|
+
and os.path.isfile(os.path.join(directory, entry))
|
|
49
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "minion-ai"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A simple agentic framework with observability and evals baked in"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"openai",
|
|
13
|
+
"pydantic",
|
|
14
|
+
"docstring-parser",
|
|
15
|
+
"litellm",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[tool.hatch.build.targets.wheel]
|
|
19
|
+
packages = ["minions"]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Repository = "https://github.com/shriyansnaik/minions"
|
|
23
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "code",
|
|
5
|
+
"execution_count": 1,
|
|
6
|
+
"id": "cc0fd4f3",
|
|
7
|
+
"metadata": {},
|
|
8
|
+
"outputs": [],
|
|
9
|
+
"source": [
|
|
10
|
+
"from minions import Minion\n",
|
|
11
|
+
"import minions as mn\n",
|
|
12
|
+
"from minions.tools import read_file, list_files"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"cell_type": "code",
|
|
17
|
+
"execution_count": 2,
|
|
18
|
+
"id": "b64ef4e1",
|
|
19
|
+
"metadata": {},
|
|
20
|
+
"outputs": [],
|
|
21
|
+
"source": [
|
|
22
|
+
"OPENAI_API_KEY = \"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\""
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"cell_type": "code",
|
|
27
|
+
"execution_count": 3,
|
|
28
|
+
"id": "b7e044de",
|
|
29
|
+
"metadata": {},
|
|
30
|
+
"outputs": [],
|
|
31
|
+
"source": [
|
|
32
|
+
"mn.init(api_key=OPENAI_API_KEY)"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"cell_type": "code",
|
|
37
|
+
"execution_count": 7,
|
|
38
|
+
"id": "c01b53a3",
|
|
39
|
+
"metadata": {},
|
|
40
|
+
"outputs": [],
|
|
41
|
+
"source": [
|
|
42
|
+
"minion = Minion(\n",
|
|
43
|
+
" model=\"gpt-5.1-chat-latest\",\n",
|
|
44
|
+
" tools=[read_file, list_files]\n",
|
|
45
|
+
")"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"cell_type": "code",
|
|
50
|
+
"execution_count": 8,
|
|
51
|
+
"id": "233d4568",
|
|
52
|
+
"metadata": {},
|
|
53
|
+
"outputs": [
|
|
54
|
+
{
|
|
55
|
+
"name": "stdout",
|
|
56
|
+
"output_type": "stream",
|
|
57
|
+
"text": [
|
|
58
|
+
"Thought: 'List directory to find requirements.txt.'\n",
|
|
59
|
+
"Tool_1: 'list_files'\n",
|
|
60
|
+
"Args: directory='.'\n",
|
|
61
|
+
"\n",
|
|
62
|
+
"Thought: 'No requirements file exists, so respond with that.'\n",
|
|
63
|
+
"Tool_1: '_finish'\n",
|
|
64
|
+
"Args: final_response='There is no requirements.txt file in the project directory.'\n",
|
|
65
|
+
"\n"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"source": [
|
|
70
|
+
"res = minion(\"can you check requirements.txt and tell me the requirements for the project\")"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"cell_type": "code",
|
|
75
|
+
"execution_count": null,
|
|
76
|
+
"id": "dda435f9",
|
|
77
|
+
"metadata": {},
|
|
78
|
+
"outputs": [],
|
|
79
|
+
"source": [
|
|
80
|
+
" "
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"metadata": {
|
|
85
|
+
"kernelspec": {
|
|
86
|
+
"display_name": ".venv (3.13.3.final.0)",
|
|
87
|
+
"language": "python",
|
|
88
|
+
"name": "python3"
|
|
89
|
+
},
|
|
90
|
+
"language_info": {
|
|
91
|
+
"codemirror_mode": {
|
|
92
|
+
"name": "ipython",
|
|
93
|
+
"version": 3
|
|
94
|
+
},
|
|
95
|
+
"file_extension": ".py",
|
|
96
|
+
"mimetype": "text/x-python",
|
|
97
|
+
"name": "python",
|
|
98
|
+
"nbconvert_exporter": "python",
|
|
99
|
+
"pygments_lexer": "ipython3",
|
|
100
|
+
"version": "3.13.3"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"nbformat": 4,
|
|
104
|
+
"nbformat_minor": 5
|
|
105
|
+
}
|