mycelium-agents 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.
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: mycelium-agents
3
+ Version: 0.1.0
4
+ Summary: The networking protocol for AI agents. Discover, communicate, and collaborate.
5
+ Author-email: Your Name <your@email.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/yourusername/mycelium
8
+ Project-URL: Documentation, https://mycelium.dev
9
+ Project-URL: Repository, https://github.com/yourusername/mycelium
10
+ Project-URL: Issues, https://github.com/yourusername/mycelium/issues
11
+ Keywords: ai,agents,protocol,networking,multi-agent,llm
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: httpx>=0.25.0
25
+ Requires-Dist: pydantic>=2.0.0
26
+ Requires-Dist: uuid7>=0.1.0
27
+ Requires-Dist: cryptography>=41.0.0
28
+ Requires-Dist: rich>=13.0.0
29
+ Requires-Dist: typer>=0.9.0
30
+ Requires-Dist: uvicorn>=0.24.0
31
+ Provides-Extra: server
32
+ Requires-Dist: fastapi>=0.104.0; extra == "server"
33
+ Requires-Dist: sqlalchemy>=2.0.0; extra == "server"
34
+ Requires-Dist: chromadb>=0.4.0; extra == "server"
35
+ Requires-Dist: aiosqlite>=0.19.0; extra == "server"
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
38
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
39
+ Requires-Dist: black>=23.0.0; extra == "dev"
40
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
41
+ Requires-Dist: mypy>=1.7.0; extra == "dev"
42
+
43
+ <div align="center">
44
+
45
+ # 🍄 Mycelium
46
+
47
+ ### The Networking Protocol for AI Agents
48
+
49
+ *Discover. Communicate. Collaborate.*
50
+
51
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
52
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
53
+ [![Protocol](https://img.shields.io/badge/protocol-v0.1.0-green.svg)]()
54
+
55
+ [Getting Started](#-quick-start) •
56
+ [Documentation](#-documentation) •
57
+ [Examples](#-examples) •
58
+ [Contributing](#-contributing) •
59
+ [Discord](#)
60
+
61
+ ---
62
+
63
+ *Like mycelium networks connect trees in a forest, this protocol connects AI agents across the internet.*
64
+
65
+ </div>
66
+
67
+ ---
68
+
69
+ ## The Problem
70
+
71
+ AI agents are everywhere. But they're all **isolated**.
@@ -0,0 +1,29 @@
1
+ <div align="center">
2
+
3
+ # 🍄 Mycelium
4
+
5
+ ### The Networking Protocol for AI Agents
6
+
7
+ *Discover. Communicate. Collaborate.*
8
+
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
11
+ [![Protocol](https://img.shields.io/badge/protocol-v0.1.0-green.svg)]()
12
+
13
+ [Getting Started](#-quick-start) •
14
+ [Documentation](#-documentation) •
15
+ [Examples](#-examples) •
16
+ [Contributing](#-contributing) •
17
+ [Discord](#)
18
+
19
+ ---
20
+
21
+ *Like mycelium networks connect trees in a forest, this protocol connects AI agents across the internet.*
22
+
23
+ </div>
24
+
25
+ ---
26
+
27
+ ## The Problem
28
+
29
+ AI agents are everywhere. But they're all **isolated**.
@@ -0,0 +1,13 @@
1
+ """Mycelium."""
2
+
3
+ # mycelium/__init__.py
4
+ from .core.agent import Agent
5
+
6
+ __all__ = ["Agent"]
7
+
8
+ # mycelium/__init__.py
9
+
10
+ from .core.agent import Agent
11
+ from .network.client import Network # <-- Ye nayi line add karni hai
12
+
13
+ __all__ = ["Agent", "Network"] # <-- Yahan Network ko bhi list mein daal de
@@ -0,0 +1 @@
1
+ """CLI tool for Mycelium."""
@@ -0,0 +1 @@
1
+ """Core components of Mycelium."""
@@ -0,0 +1,438 @@
1
+ """
2
+ Agent — The core building block of Mycelium.
3
+
4
+ This is what developers interact with the most.
5
+ Create an agent, add capabilities, register it, serve requests.
6
+
7
+ Usage:
8
+ from mycelium import Agent
9
+
10
+ agent = Agent(
11
+ name="Translator",
12
+ description="Translates text between languages"
13
+ )
14
+
15
+ @agent.on("translate")
16
+ def handle_translate(text: str, to: str):
17
+ # your translation logic
18
+ return {"translated": do_translate(text, to)}
19
+
20
+ agent.register()
21
+ agent.serve()
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import asyncio
27
+ import signal
28
+ import time
29
+ from typing import Any, Callable, Optional
30
+
31
+
32
+ import httpx
33
+ from rich.console import Console
34
+ from rich.panel import Panel
35
+ from rich.table import Table
36
+
37
+ from mycelium.core.card import AgentCard, PricingInfo
38
+ from mycelium.core.capability import Capability, CapabilitySchema
39
+ from mycelium.core.message import Message, MessageType, StatusCode
40
+ from mycelium.core.errors import (
41
+ CapabilityNotFoundError,
42
+ RegistrationError,
43
+ CommunicationError,
44
+ )
45
+
46
+ console = Console()
47
+
48
+ DEFAULT_REGISTRY = "http://localhost:8000"
49
+
50
+
51
+ class Agent:
52
+ """
53
+ A Mycelium AI Agent.
54
+
55
+ Create agents that can:
56
+ - Register on the Mycelium network
57
+ - Be discovered by other agents
58
+ - Receive and handle requests
59
+ - Send requests to other agents
60
+ - Build trust through successful interactions
61
+ """
62
+
63
+ def __init__(
64
+ self,
65
+ name: str,
66
+ description: str,
67
+ version: str = "0.1.0",
68
+ author: Optional[str] = None,
69
+ languages: Optional[list[str]] = None,
70
+ tags: Optional[list[str]] = None,
71
+ pricing: Optional[PricingInfo] = None,
72
+ registry_url: str = DEFAULT_REGISTRY,
73
+ endpoint: Optional[str] = None,
74
+ ):
75
+ """
76
+ Create a new Mycelium agent.
77
+
78
+ Args:
79
+ name: Human-readable name for your agent
80
+ description: What does your agent do?
81
+ version: Semantic version of your agent
82
+ author: Your name or email
83
+ languages: Languages your agent supports
84
+ tags: Search tags for discovery
85
+ pricing: How you charge for requests
86
+ registry_url: URL of the Mycelium registry
87
+ endpoint: URL where your agent receives requests
88
+ """
89
+ self._capabilities: dict[str, Capability] = {}
90
+ self._handlers: dict[str, Callable] = {}
91
+ self._registry_url = registry_url
92
+ self._is_registered = False
93
+ self._is_serving = False
94
+
95
+ # Build the Agent Card
96
+ self.card = AgentCard(
97
+ name=name,
98
+ description=description,
99
+ version=version,
100
+ author=author,
101
+ languages=languages or ["english"],
102
+ tags=tags or [],
103
+ pricing=pricing or PricingInfo(),
104
+ endpoint=endpoint,
105
+ )
106
+
107
+ console.print(
108
+ f"[green]🍄 Agent '{name}' created![/green] "
109
+ f"ID: [cyan]{self.card.agent_id}[/cyan]"
110
+ )
111
+
112
+ @property
113
+ def agent_id(self) -> str:
114
+ return self.card.agent_id
115
+
116
+ @property
117
+ def name(self) -> str:
118
+ return self.card.name
119
+
120
+ # =========================================================
121
+ # CAPABILITY REGISTRATION
122
+ # =========================================================
123
+
124
+ def on(
125
+ self,
126
+ capability_name: str,
127
+ description: str = "",
128
+ input_schema: Optional[dict[str, str]] = None,
129
+ output_schema: Optional[dict[str, str]] = None,
130
+ tags: Optional[list[str]] = None,
131
+ ) -> Callable:
132
+ """
133
+ Decorator to register a capability handler.
134
+
135
+ Usage:
136
+ @agent.on("translate", description="Translates text")
137
+ def handle_translate(text: str, to: str):
138
+ return {"translated": my_translate(text, to)}
139
+ """
140
+
141
+ def decorator(func: Callable) -> Callable:
142
+ cap = Capability(
143
+ name=capability_name,
144
+ description=description or f"Handler for {capability_name}",
145
+ input_schema=CapabilitySchema(
146
+ fields=input_schema or {},
147
+ required=list((input_schema or {}).keys()),
148
+ ),
149
+ output_schema=CapabilitySchema(
150
+ fields=output_schema or {},
151
+ ),
152
+ tags=tags or [],
153
+ )
154
+ cap.set_handler(func)
155
+
156
+ self._capabilities[capability_name] = cap
157
+ self._handlers[capability_name] = func
158
+
159
+ # Update card
160
+ self.card.capabilities.append(cap.to_card_dict())
161
+
162
+ console.print(
163
+ f" [blue]⚡ Capability registered:[/blue] "
164
+ f"[bold]{capability_name}[/bold] — {cap.description}"
165
+ )
166
+
167
+ return func
168
+
169
+ return decorator
170
+
171
+ def add_capability(
172
+ self,
173
+ name: str,
174
+ handler: Callable,
175
+ description: str = "",
176
+ input_schema: Optional[dict[str, str]] = None,
177
+ output_schema: Optional[dict[str, str]] = None,
178
+ ) -> None:
179
+ """Add a capability without using the decorator."""
180
+ cap = Capability(
181
+ name=name,
182
+ description=description or f"Handler for {name}",
183
+ input_schema=CapabilitySchema(
184
+ fields=input_schema or {},
185
+ required=list((input_schema or {}).keys()),
186
+ ),
187
+ output_schema=CapabilitySchema(fields=output_schema or {}),
188
+ )
189
+ cap.set_handler(handler)
190
+ self._capabilities[name] = cap
191
+ self._handlers[name] = handler
192
+ self.card.capabilities.append(cap.to_card_dict())
193
+
194
+ # =========================================================
195
+ # NETWORK OPERATIONS
196
+ # =========================================================
197
+
198
+ async def register_async(self) -> bool:
199
+ """Register this agent on the Mycelium network (async)."""
200
+ try:
201
+ async with httpx.AsyncClient(timeout=30.0) as client:
202
+ response = await client.post(
203
+ f"{self._registry_url}/api/v1/agents/register",
204
+ json=self.card.model_dump(mode="json"),
205
+ )
206
+
207
+ if response.status_code == 200:
208
+ data = response.json()
209
+ self._is_registered = True
210
+ self.card.status = "online"
211
+
212
+ console.print(
213
+ Panel(
214
+ f"[green]✅ Agent '{self.name}' registered on Mycelium![/green]\n"
215
+ f" Agent ID: [cyan]{self.agent_id}[/cyan]\n"
216
+ f" Capabilities: {len(self._capabilities)}\n"
217
+ f" Registry: {self._registry_url}",
218
+ title="🍄 Registration Successful",
219
+ border_style="green",
220
+ )
221
+ )
222
+ return True
223
+ else:
224
+ raise RegistrationError(
225
+ f"Registry returned {response.status_code}: {response.text}"
226
+ )
227
+
228
+ except httpx.ConnectError:
229
+ raise RegistrationError(
230
+ f"Cannot connect to registry at {self._registry_url}. "
231
+ f"Is the registry server running?"
232
+ )
233
+ except Exception as e:
234
+ raise RegistrationError(str(e))
235
+
236
+ def register(self) -> bool:
237
+ """Register this agent on the Mycelium network (sync wrapper)."""
238
+ return asyncio.run(self.register_async())
239
+
240
+ # =========================================================
241
+ # HANDLE INCOMING REQUESTS
242
+ # =========================================================
243
+
244
+ async def handle_message(self, message: Message) -> Message:
245
+ """Process an incoming message and return a response."""
246
+ start_time = time.time()
247
+
248
+ # Handle ping
249
+ if message.envelope.message_type == MessageType.PING:
250
+ return Message.create_response(
251
+ original=message,
252
+ outputs={"pong": True, "agent": self.name},
253
+ status=StatusCode.SUCCESS,
254
+ )
255
+
256
+ # Handle capability request
257
+ if message.envelope.message_type == MessageType.REQUEST:
258
+ capability_name = message.payload.get("capability")
259
+ inputs = message.payload.get("inputs", {})
260
+
261
+ if capability_name not in self._capabilities:
262
+ return Message.create_error(
263
+ original=message,
264
+ error_message=f"Capability '{capability_name}' not found",
265
+ status=StatusCode.CAPABILITY_NOT_FOUND,
266
+ )
267
+
268
+ try:
269
+ capability = self._capabilities[capability_name]
270
+ result = await capability.execute(inputs)
271
+ processing_time = (time.time() - start_time) * 1000
272
+
273
+ # Update stats
274
+ self.card.total_requests_served += 1
275
+
276
+ return Message.create_response(
277
+ original=message,
278
+ outputs=result if isinstance(result, dict) else {"result": result},
279
+ status=StatusCode.SUCCESS,
280
+ processing_time_ms=processing_time,
281
+ )
282
+
283
+ except Exception as e:
284
+ return Message.create_error(
285
+ original=message,
286
+ error_message=str(e),
287
+ status=StatusCode.ERROR,
288
+ )
289
+
290
+ return Message.create_error(
291
+ original=message,
292
+ error_message=f"Unknown message type: {message.envelope.message_type}",
293
+ )
294
+
295
+ # =========================================================
296
+ # SEND REQUESTS TO OTHER AGENTS
297
+ # =========================================================
298
+
299
+ async def request_async(
300
+ self,
301
+ agent_id: str,
302
+ capability: str,
303
+ inputs: dict[str, Any],
304
+ timeout: int = 30,
305
+ ) -> dict[str, Any]:
306
+ """Send a request to another agent on the network."""
307
+ message = Message.create_request(
308
+ from_agent=self.agent_id,
309
+ to_agent=agent_id,
310
+ capability=capability,
311
+ inputs=inputs,
312
+ )
313
+
314
+ try:
315
+ async with httpx.AsyncClient(timeout=timeout) as client:
316
+ response = await client.post(
317
+ f"{self._registry_url}/api/v1/messages/send",
318
+ json=message.model_dump(mode="json"),
319
+ )
320
+
321
+ if response.status_code == 200:
322
+ data = response.json()
323
+ return data.get("payload", {}).get("outputs", {})
324
+ else:
325
+ raise CommunicationError(
326
+ self.agent_id,
327
+ agent_id,
328
+ f"Registry returned {response.status_code}",
329
+ )
330
+
331
+ except httpx.ConnectError:
332
+ raise CommunicationError(
333
+ self.agent_id,
334
+ agent_id,
335
+ "Cannot connect to registry",
336
+ )
337
+
338
+ def request(
339
+ self,
340
+ agent_id: str,
341
+ capability: str,
342
+ inputs: dict[str, Any],
343
+ timeout: int = 30,
344
+ ) -> dict[str, Any]:
345
+ """Send a request to another agent (sync wrapper)."""
346
+ return asyncio.run(
347
+ self.request_async(agent_id, capability, inputs, timeout)
348
+ )
349
+
350
+ # =========================================================
351
+ # SERVE (Keep agent alive and listening)
352
+ # =========================================================
353
+
354
+ async def _serve_async(self, host: str = "0.0.0.0", port: int = 8001):
355
+ """Start serving requests (async)."""
356
+ try:
357
+ from fastapi import FastAPI
358
+ import uvicorn
359
+ except ImportError:
360
+ raise ImportError(
361
+ "FastAPI and uvicorn required for serving. "
362
+ "Install with: pip install mycelium-ai[server]"
363
+ )
364
+
365
+ app = FastAPI(title=f"🍄 {self.name} — Mycelium Agent")
366
+
367
+ @app.post("/mycelium/handle")
368
+ async def handle_request(raw_message: dict):
369
+ message = Message(**raw_message)
370
+ response = await self.handle_message(message)
371
+ return response.model_dump(mode="json")
372
+
373
+ @app.get("/mycelium/card")
374
+ async def get_card():
375
+ return self.card.model_dump(mode="json")
376
+
377
+ @app.get("/mycelium/health")
378
+ async def health():
379
+ return {
380
+ "status": "healthy",
381
+ "agent": self.name,
382
+ "agent_id": self.agent_id,
383
+ "capabilities": list(self._capabilities.keys()),
384
+ "requests_served": self.card.total_requests_served,
385
+ }
386
+
387
+ self._is_serving = True
388
+ self.card.status = "online"
389
+ self.card.endpoint = f"http://{host}:{port}"
390
+
391
+ console.print(
392
+ Panel(
393
+ f"[green]🍄 Agent '{self.name}' is now LIVE![/green]\n\n"
394
+ f" Endpoint: [cyan]http://{host}:{port}[/cyan]\n"
395
+ f" Capabilities: {', '.join(self._capabilities.keys())}\n"
396
+ f" Agent ID: {self.agent_id}\n\n"
397
+ f" [dim]Press Ctrl+C to stop[/dim]",
398
+ title="🌐 Agent Serving",
399
+ border_style="green",
400
+ )
401
+ )
402
+
403
+ config = uvicorn.Config(app, host=host, port=port, log_level="info")
404
+ server = uvicorn.Server(config)
405
+ await server.serve()
406
+
407
+ def serve(self, host: str = "0.0.0.0", port: int = 8001):
408
+ """Start serving requests (sync wrapper)."""
409
+ asyncio.run(self._serve_async(host, port))
410
+
411
+ # =========================================================
412
+ # DISPLAY
413
+ # =========================================================
414
+
415
+ def info(self) -> None:
416
+ """Print agent information in a nice format."""
417
+ table = Table(title=f"🍄 Agent: {self.name}")
418
+
419
+ table.add_column("Property", style="cyan")
420
+ table.add_column("Value", style="white")
421
+
422
+ table.add_row("Agent ID", self.card.agent_id)
423
+ table.add_row("Version", self.card.version)
424
+ table.add_row("Description", self.card.description)
425
+ table.add_row("Status", self.card.status)
426
+ table.add_row("Capabilities", ", ".join(self._capabilities.keys()) or "None")
427
+ table.add_row("Languages", ", ".join(self.card.languages))
428
+ table.add_row("Tags", ", ".join(self.card.tags) or "None")
429
+ table.add_row("Trust Score", f"{'⭐' * int(self.card.trust_score)} ({self.card.trust_score}/5.0)")
430
+ table.add_row("Requests Served", str(self.card.total_requests_served))
431
+ table.add_row("Registered", "✅ Yes" if self._is_registered else "❌ No")
432
+ table.add_row("Serving", "✅ Yes" if self._is_serving else "❌ No")
433
+
434
+ console.print(table)
435
+
436
+ def __repr__(self) -> str:
437
+ caps = ", ".join(self._capabilities.keys())
438
+ return f"Agent(name='{self.name}', capabilities=[{caps}])"
@@ -0,0 +1,101 @@
1
+ """
2
+ Capability — What an agent CAN DO.
3
+
4
+ Each agent has one or more capabilities.
5
+ Think of these as "skills" that other agents can request.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Callable, Optional
11
+ from pydantic import BaseModel, Field
12
+
13
+
14
+ class CapabilitySchema(BaseModel):
15
+ """Schema definition for capability inputs/outputs."""
16
+
17
+ fields: dict[str, str] = Field(
18
+ default_factory=dict,
19
+ description="Field name → type description mapping",
20
+ )
21
+ required: list[str] = Field(
22
+ default_factory=list,
23
+ description="Required field names",
24
+ )
25
+ example: Optional[dict[str, Any]] = None
26
+
27
+
28
+ class Capability(BaseModel):
29
+ """
30
+ A single capability of an agent.
31
+
32
+ Example:
33
+ cap = Capability(
34
+ name="translate",
35
+ description="Translates text between languages",
36
+ input_schema=CapabilitySchema(
37
+ fields={"text": "string", "to": "language code"},
38
+ required=["text", "to"],
39
+ example={"text": "Hello", "to": "hi"}
40
+ ),
41
+ output_schema=CapabilitySchema(
42
+ fields={"translated": "string", "confidence": "float"},
43
+ example={"translated": "नमस्ते", "confidence": 0.98}
44
+ )
45
+ )
46
+ """
47
+
48
+ name: str = Field(..., description="Unique capability name")
49
+ description: str = Field(..., description="What this capability does")
50
+ input_schema: CapabilitySchema = Field(
51
+ default_factory=CapabilitySchema,
52
+ description="Expected input format",
53
+ )
54
+ output_schema: CapabilitySchema = Field(
55
+ default_factory=CapabilitySchema,
56
+ description="Expected output format",
57
+ )
58
+ tags: list[str] = Field(default_factory=list)
59
+ version: str = Field(default="1.0.0")
60
+ is_async: bool = Field(default=False, description="Whether this capability runs async")
61
+ avg_response_time_ms: Optional[float] = Field(
62
+ default=None,
63
+ description="Average response time in milliseconds",
64
+ )
65
+ cost_per_request: Optional[float] = Field(
66
+ default=None,
67
+ description="Cost per request in USD",
68
+ )
69
+
70
+ # Internal — not serialized to network
71
+ _handler: Optional[Callable] = None
72
+
73
+ class Config:
74
+ arbitrary_types_allowed = True
75
+
76
+ def set_handler(self, handler: Callable) -> None:
77
+ """Attach a handler function to this capability."""
78
+ self._handler = handler
79
+
80
+ async def execute(self, inputs: dict[str, Any]) -> dict[str, Any]:
81
+ """Execute this capability with given inputs."""
82
+ if self._handler is None:
83
+ raise ValueError(f"No handler registered for capability '{self.name}'")
84
+
85
+ # Validate required fields
86
+ for field in self.input_schema.required:
87
+ if field not in inputs:
88
+ raise ValueError(
89
+ f"Missing required field '{field}' for capability '{self.name}'"
90
+ )
91
+
92
+ import asyncio
93
+
94
+ if asyncio.iscoroutinefunction(self._handler):
95
+ return await self._handler(**inputs)
96
+ else:
97
+ return self._handler(**inputs)
98
+
99
+ def to_card_dict(self) -> dict:
100
+ """Export capability info for the Agent Card (no handler)."""
101
+ return self.model_dump(exclude={"_handler"})