agentstr 0.1.7__py3-none-any.whl → 0.1.8__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.
- agentstr/__init__.py +30 -1
- agentstr/marketplace.py +1060 -92
- agentstr/nostr.py +260 -74
- {agentstr-0.1.7.dist-info → agentstr-0.1.8.dist-info}/LICENSE +1 -1
- agentstr-0.1.8.dist-info/METADATA +110 -0
- agentstr-0.1.8.dist-info/RECORD +8 -0
- agentstr-0.1.7.dist-info/METADATA +0 -111
- agentstr-0.1.7.dist-info/RECORD +0 -8
- {agentstr-0.1.7.dist-info → agentstr-0.1.8.dist-info}/WHEEL +0 -0
- {agentstr-0.1.7.dist-info → agentstr-0.1.8.dist-info}/top_level.txt +0 -0
agentstr/nostr.py
CHANGED
@@ -1,53 +1,193 @@
|
|
1
|
+
import logging
|
1
2
|
from typing import Optional
|
2
|
-
from os import getenv
|
3
|
-
import logging
|
4
3
|
|
5
|
-
try:
|
4
|
+
try:
|
6
5
|
import asyncio
|
7
6
|
except ImportError:
|
8
|
-
raise ImportError(
|
7
|
+
raise ImportError(
|
8
|
+
"`asyncio` not installed. Please install using `pip install asyncio`"
|
9
|
+
)
|
9
10
|
|
10
11
|
try:
|
11
|
-
from nostr_sdk import
|
12
|
+
from nostr_sdk import (
|
13
|
+
Client,
|
14
|
+
Coordinate,
|
15
|
+
Event,
|
16
|
+
EventBuilder,
|
17
|
+
EventId,
|
18
|
+
Keys,
|
19
|
+
Kind,
|
20
|
+
Metadata,
|
21
|
+
NostrSigner,
|
22
|
+
ProductData,
|
23
|
+
PublicKey,
|
24
|
+
ShippingCost,
|
25
|
+
ShippingMethod,
|
26
|
+
StallData,
|
27
|
+
Tag,
|
28
|
+
Timestamp,
|
29
|
+
)
|
30
|
+
|
12
31
|
except ImportError:
|
13
|
-
raise ImportError(
|
32
|
+
raise ImportError(
|
33
|
+
"`nostr_sdk` not installed. Please install using `pip install nostr_sdk`"
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
class NostrClient:
|
38
|
+
"""
|
39
|
+
NostrClient implements the set of Nostr utilities required for higher level functions implementing
|
40
|
+
like the Marketplace.
|
41
|
+
|
42
|
+
Nostr is an asynchronous communication protocol. To hide this, NostrClient exposes synchronous functions.
|
43
|
+
Users of the NostrClient should ignore `_async_` functions which are for internal purposes only.
|
44
|
+
"""
|
14
45
|
|
15
|
-
class NostrClient():
|
16
|
-
|
17
46
|
logger = logging.getLogger("NostrClient")
|
18
47
|
ERROR: str = "ERROR"
|
19
48
|
SUCCESS: str = "SUCCESS"
|
20
|
-
|
49
|
+
|
21
50
|
def __init__(
|
22
51
|
self,
|
23
|
-
relay: str
|
24
|
-
nsec: str
|
25
|
-
):
|
26
|
-
"""
|
52
|
+
relay: str,
|
53
|
+
nsec: str,
|
54
|
+
) -> None:
|
55
|
+
"""
|
56
|
+
Initialize the Nostr client.
|
27
57
|
|
28
58
|
Args:
|
29
|
-
relay: Nostr relay that the client will connect to
|
59
|
+
relay: Nostr relay that the client will connect to
|
30
60
|
nsec: Nostr private key in bech32 format
|
31
61
|
"""
|
32
62
|
# Set log handling
|
33
63
|
if not NostrClient.logger.hasHandlers():
|
34
64
|
console_handler = logging.StreamHandler()
|
35
65
|
console_handler.setLevel(logging.INFO)
|
36
|
-
formatter = logging.Formatter(
|
66
|
+
formatter = logging.Formatter(
|
67
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
68
|
+
)
|
37
69
|
console_handler.setFormatter(formatter)
|
38
70
|
NostrClient.logger.addHandler(console_handler)
|
39
|
-
|
71
|
+
|
72
|
+
# configure relay and keys for the client
|
40
73
|
self.relay = relay
|
41
74
|
self.keys = Keys.parse(nsec)
|
42
75
|
self.nostr_signer = NostrSigner.keys(self.keys)
|
43
76
|
self.client = Client(self.nostr_signer)
|
44
|
-
|
45
77
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
78
|
+
def delete_event(self, event_id: EventId, reason: Optional[str] = None) -> EventId:
|
79
|
+
"""
|
80
|
+
Requests the relay to delete an event. Relays may or may not honor the request.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
event_id: EventId associated with the event to be deleted
|
84
|
+
reason: optional reason for deleting the event
|
85
|
+
|
86
|
+
Returns:
|
87
|
+
EventId: if of the event requesting the deletion of event_id
|
88
|
+
|
89
|
+
Raises:
|
90
|
+
RuntimeError: if the deletion event can't be published
|
91
|
+
"""
|
92
|
+
event_builder = EventBuilder.delete(ids=[event_id], reason=reason)
|
93
|
+
# Run the async publishing function synchronously
|
94
|
+
return asyncio.run(self._async_publish_event(event_builder))
|
95
|
+
|
96
|
+
def publish_event(self, event_builder: EventBuilder) -> EventId:
|
97
|
+
"""
|
98
|
+
Publish generic Nostr event to the relay
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
EventId: event id if successful or NostrClient.ERROR if unsuccesful
|
102
|
+
|
103
|
+
Raises:
|
104
|
+
RuntimeError: if the product can't be published
|
105
|
+
"""
|
106
|
+
# Run the async publishing function synchronously
|
107
|
+
return asyncio.run(self._async_publish_event(event_builder))
|
108
|
+
|
109
|
+
def publish_note(self, text: str) -> EventId:
|
110
|
+
"""Publish note with event kind 1
|
111
|
+
|
112
|
+
Args:
|
113
|
+
text: text to be published as kind 1 event
|
114
|
+
|
115
|
+
Returns:
|
116
|
+
EventId: EventId if successful or NostrClient.ERROR if unsuccesful
|
117
|
+
|
118
|
+
Raises:
|
119
|
+
RuntimeError: if the product can't be published
|
120
|
+
"""
|
121
|
+
# Run the async publishing function synchronously
|
122
|
+
return asyncio.run(self._async_publish_note(text))
|
123
|
+
|
124
|
+
def publish_product(self, product: ProductData) -> EventId:
|
125
|
+
"""
|
126
|
+
Create or update a NIP-15 Marketplace product with event kind 30018
|
127
|
+
|
128
|
+
Args:
|
129
|
+
product: product to be published
|
130
|
+
|
131
|
+
Returns:
|
132
|
+
EventId: event id if successful or NostrClient.ERROR if unsuccesful
|
133
|
+
|
134
|
+
Raises:
|
135
|
+
RuntimeError: if the product can't be published
|
136
|
+
"""
|
137
|
+
# Run the async publishing function synchronously
|
138
|
+
return asyncio.run(self._async_publish_product(product))
|
139
|
+
|
140
|
+
def publish_profile(self, name: str, about: str, picture: str) -> EventId:
|
141
|
+
"""
|
142
|
+
Publish a Nostr profile with event kind 0
|
143
|
+
|
144
|
+
Args:
|
145
|
+
name: name of the Nostr profile
|
146
|
+
about: brief description about the profile
|
147
|
+
picture: url to a png file with a picture for the profile
|
148
|
+
|
149
|
+
Returns:
|
150
|
+
EventId: event id if successful or NostrClient.ERROR if unsuccesful
|
151
|
+
|
152
|
+
Raises:
|
153
|
+
RuntimeError: if the profile can't be published
|
154
|
+
"""
|
155
|
+
# Run the async publishing function synchronously
|
156
|
+
return asyncio.run(self._async_publish_profile(name, about, picture))
|
157
|
+
|
158
|
+
def publish_stall(self, stall: StallData) -> EventId:
|
159
|
+
"""Publish a stall to nostr
|
160
|
+
|
161
|
+
Args:
|
162
|
+
stall: stall to be published
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
EventId: event id if successful or NostrClient.ERROR if unsuccesful
|
166
|
+
"""
|
167
|
+
try:
|
168
|
+
return asyncio.run(self._async_publish_stall(stall))
|
169
|
+
except Exception as e:
|
170
|
+
self.logger.error(f"Failed to publish stall: {e}")
|
171
|
+
return NostrClient.ERROR
|
172
|
+
|
173
|
+
@classmethod
|
174
|
+
def set_logging_level(cls, logging_level: int) -> None:
|
175
|
+
"""Set the logging level for the NostrClient logger.
|
176
|
+
|
177
|
+
Args:
|
178
|
+
logging_level: The logging level (e.g., logging.DEBUG, logging.INFO)
|
179
|
+
"""
|
180
|
+
cls.logger.setLevel(logging_level)
|
181
|
+
for handler in cls.logger.handlers:
|
182
|
+
handler.setLevel(logging_level)
|
183
|
+
cls.logger.info(f"Logging level set to {logging.getLevelName(logging_level)}")
|
184
|
+
|
185
|
+
# ----------------------------------------------------------------------------------------------
|
186
|
+
# --*-- async functions for internal use only. Developers should use synchronous functions above
|
187
|
+
# ----------------------------------------------------------------------------------------------
|
188
|
+
|
189
|
+
async def _async_connect(self) -> str:
|
190
|
+
"""Asynchronous function to add relay to the NostrClient instance and connect to it.
|
51
191
|
|
52
192
|
Returns:
|
53
193
|
str: NostrClient.SUCCESS or NostrClient.ERROR
|
@@ -59,83 +199,129 @@ class NostrClient():
|
|
59
199
|
NostrClient.logger.info("Connected to relay.")
|
60
200
|
return NostrClient.SUCCESS
|
61
201
|
except Exception as e:
|
62
|
-
NostrClient.logger.error(
|
202
|
+
NostrClient.logger.error(
|
203
|
+
f"Unable to connect to relay {self.relay}. Exception: {e}."
|
204
|
+
)
|
63
205
|
return NostrClient.ERROR
|
64
|
-
|
65
|
-
async def publish_text_note(
|
66
|
-
self,
|
67
|
-
text: str
|
68
|
-
) -> str:
|
69
|
-
|
70
|
-
"""Publish kind 1 event (text note) to the relay
|
71
206
|
|
72
|
-
|
73
|
-
|
207
|
+
async def _async_publish_event(self, event_builder: EventBuilder) -> EventId:
|
208
|
+
"""
|
209
|
+
Publish generic Nostr event to the relay
|
74
210
|
|
75
211
|
Returns:
|
76
|
-
|
212
|
+
EventId: event id of the published event
|
213
|
+
|
214
|
+
Raises:
|
215
|
+
RuntimeError: if the event can't be published
|
77
216
|
"""
|
78
|
-
|
217
|
+
connected = await self._async_connect()
|
218
|
+
|
219
|
+
if connected == NostrClient.ERROR:
|
220
|
+
raise RuntimeError("Unable to connect to the relay")
|
79
221
|
|
80
222
|
try:
|
81
|
-
output = await self.client.send_event_builder(
|
82
|
-
|
83
|
-
|
223
|
+
output = await self.client.send_event_builder(event_builder)
|
224
|
+
if len(output.success) > 0:
|
225
|
+
NostrClient.logger.info(
|
226
|
+
f"Event published with event id: {output.id.to_bech32()}"
|
227
|
+
)
|
228
|
+
return output.id
|
229
|
+
else:
|
230
|
+
raise RuntimeError("Unable to publish event")
|
84
231
|
except Exception as e:
|
85
|
-
NostrClient.logger.error(
|
86
|
-
|
232
|
+
NostrClient.logger.error(
|
233
|
+
f"NostrClient instance not properly initialized. Exception: {e}."
|
234
|
+
)
|
235
|
+
raise RuntimeError(
|
236
|
+
f"NostrClient instance not properly initialized. Exception: {e}."
|
237
|
+
)
|
87
238
|
|
88
|
-
async def
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
239
|
+
async def _async_publish_note(self, text: str) -> EventId:
|
240
|
+
"""
|
241
|
+
Asynchronous funcion to publish kind 1 event (text note) to the relay
|
242
|
+
|
243
|
+
Args:
|
244
|
+
text: text to be published as kind 1 event
|
94
245
|
|
95
246
|
Returns:
|
96
|
-
|
247
|
+
EventId: event id if successful or NostrClient.ERROR if unsuccesful
|
248
|
+
|
249
|
+
Raises:
|
250
|
+
RuntimeError: if the event can't be published
|
97
251
|
"""
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
252
|
+
event_builder = EventBuilder.text_note(text)
|
253
|
+
return await self._async_publish_event(event_builder)
|
254
|
+
|
255
|
+
async def _async_publish_product(self, product: ProductData) -> EventId:
|
256
|
+
"""
|
257
|
+
Asynchronous function to create or update a NIP-15 Marketplace product with event kind 30018
|
258
|
+
|
259
|
+
Args:
|
260
|
+
product: product to publish
|
261
|
+
|
262
|
+
Returns:
|
263
|
+
EventId: event id if successful or NostrClient.ERROR if unsuccesfull
|
264
|
+
|
265
|
+
Raises:
|
266
|
+
RuntimeError: if the product can't be published
|
267
|
+
"""
|
268
|
+
coordinate_tag = Coordinate(
|
269
|
+
Kind(30017), self.keys.public_key(), product.stall_id
|
270
|
+
)
|
271
|
+
|
272
|
+
# EventBuilder.product_data() has a bug with tag handling.
|
273
|
+
# We use the function to create the content field and discard the eventbuilder
|
274
|
+
bad_event_builder = EventBuilder.product_data(product)
|
275
|
+
|
276
|
+
# create an event from bad_event_builder to extract the content - not broadcasted
|
277
|
+
bad_event = await self.client.sign_event_builder(bad_event_builder)
|
278
|
+
content = bad_event.content()
|
279
|
+
|
280
|
+
# build a new event with the right tags and the content
|
281
|
+
good_event_builder = EventBuilder(Kind(30018), content).tags(
|
282
|
+
[Tag.identifier(product.id), Tag.coordinate(coordinate_tag)]
|
283
|
+
)
|
284
|
+
self.logger.info("Product event: " + str(good_event_builder))
|
285
|
+
return await self._async_publish_event(good_event_builder)
|
286
|
+
|
287
|
+
async def _async_publish_profile(
|
288
|
+
self, name: str, about: str, picture: str
|
289
|
+
) -> EventId:
|
290
|
+
"""
|
291
|
+
Asynchronous function to publish a Nostr profile with event kind 0
|
108
292
|
|
109
293
|
Args:
|
110
294
|
name: name of the Nostr profile
|
111
295
|
about: brief description about the profile
|
112
296
|
picture: url to a png file with a picture for the profile
|
113
|
-
|
297
|
+
|
114
298
|
Returns:
|
115
|
-
|
299
|
+
EventId: event id if successful or NostrClient.ERROR if unsuccesful
|
300
|
+
|
301
|
+
Raises:
|
302
|
+
RuntimeError: if the profile can't be published
|
116
303
|
"""
|
117
304
|
metadata_content = Metadata().set_name(name)
|
118
305
|
metadata_content = metadata_content.set_about(about)
|
119
306
|
metadata_content = metadata_content.set_picture(picture)
|
120
307
|
|
121
|
-
|
122
|
-
|
123
|
-
output = await self.client.send_event_builder(builder)
|
124
|
-
NostrClient.logger.info(f"Profile note published with event id: {output.id.to_bech32()}")
|
125
|
-
return output.id.to_bech32()
|
126
|
-
except Exception as e:
|
127
|
-
NostrClient.logger.error(f"Unable to publish profile to relay {self.relay}. Exception: {e}.")
|
128
|
-
return NostrClient.ERROR
|
308
|
+
event_builder = EventBuilder.metadata(metadata_content)
|
309
|
+
return await self._async_publish_event(event_builder)
|
129
310
|
|
130
|
-
|
131
|
-
def set_logging_level(cls, logging_level: int):
|
311
|
+
async def _async_publish_stall(self, stall: StallData) -> EventId:
|
132
312
|
"""
|
133
|
-
|
313
|
+
Asynchronous function to create or update a NIP-15 Marketplace stall with event kind 30017
|
134
314
|
|
135
315
|
Args:
|
136
|
-
|
316
|
+
stall: stall to be published
|
317
|
+
|
318
|
+
Returns:
|
319
|
+
EventId: event id if successful or NostrClient.ERROR if unsuccesfull
|
320
|
+
|
321
|
+
Raises:
|
322
|
+
RuntimeError: if the profile can't be published
|
137
323
|
"""
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
324
|
+
|
325
|
+
self.logger.info(f"Stall: {stall}")
|
326
|
+
event_builder = EventBuilder.stall_data(stall)
|
327
|
+
return await self._async_publish_event(event_builder)
|
@@ -0,0 +1,110 @@
|
|
1
|
+
Metadata-Version: 2.2
|
2
|
+
Name: agentstr
|
3
|
+
Version: 0.1.8
|
4
|
+
Summary: Nostr extension for Phidata AI agents
|
5
|
+
Project-URL: Homepage, https://github.com/synvya/agentstr
|
6
|
+
Project-URL: Documentation, https://github.com/synvya/agentstr#readme
|
7
|
+
Project-URL: BugTracker, https://github.com/synvya/agentstr/issues
|
8
|
+
Requires-Python: <3.13,>=3.9
|
9
|
+
Description-Content-Type: text/markdown
|
10
|
+
License-File: LICENSE
|
11
|
+
Requires-Dist: phidata>=2.7.0
|
12
|
+
Requires-Dist: openai>=1.50.0
|
13
|
+
Requires-Dist: packaging>=24.0
|
14
|
+
Requires-Dist: nostr-sdk>=0.38.0
|
15
|
+
Requires-Dist: pydantic>=2.0.0
|
16
|
+
Provides-Extra: dev
|
17
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
18
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
19
|
+
Requires-Dist: isort>=5.0; extra == "dev"
|
20
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
21
|
+
Requires-Dist: python-dotenv>=1.0; extra == "dev"
|
22
|
+
|
23
|
+
# AgentStr
|
24
|
+
|
25
|
+
AgentStr is an extension of [Phidata](https://www.phidata.com) AI agents that enables peer-to-peer agent communication using the Nostr protocol.
|
26
|
+
|
27
|
+
## Overview
|
28
|
+
|
29
|
+
AgentStr allows AI agents operated by different organizations to communicate and collaborate. For example:
|
30
|
+
- Agent A from Company A can coordinate with Agent B from Company B to execute a transaction
|
31
|
+
- Agents can discover and interact with each other through the decentralized Nostr network
|
32
|
+
- No central authority or intermediary required
|
33
|
+
|
34
|
+
## Project Structure
|
35
|
+
|
36
|
+
```
|
37
|
+
agentstr/
|
38
|
+
├── src/ # Source code
|
39
|
+
│ └── agentstr/
|
40
|
+
│ ├── __init__.py
|
41
|
+
│ ├── marketplace.py
|
42
|
+
│ └── nostr.py
|
43
|
+
├── tests/ # Test files
|
44
|
+
├── docs/ # Documentation
|
45
|
+
├── examples/ # Example implementations
|
46
|
+
└── ...
|
47
|
+
```
|
48
|
+
|
49
|
+
## Features
|
50
|
+
|
51
|
+
### Current Features
|
52
|
+
- Create Merchant agents with Nostr identities
|
53
|
+
- Publish and manage merchant products using [NIP-15](https://github.com/nostr-protocol/nips/blob/master/15.md) marketplace protocol
|
54
|
+
- Create merchant stalls to organize products
|
55
|
+
- Handle shipping zones and costs
|
56
|
+
- Secure communication using Nostr keys
|
57
|
+
|
58
|
+
### Roadmap
|
59
|
+
- [ ] Create marketplace with stalls
|
60
|
+
- [ ] Create Buyer agents
|
61
|
+
- [ ] Enable merchants to define products
|
62
|
+
- [ ] Add customer toolkit for buyers
|
63
|
+
- [ ] Support additional Nostr NIPs
|
64
|
+
- [ ] Add more agent interaction patterns
|
65
|
+
|
66
|
+
## Installation
|
67
|
+
|
68
|
+
```bash
|
69
|
+
# Create a new python environment
|
70
|
+
python3 -m venv ~/.venvs/aienv
|
71
|
+
source ~/.venvs/aienv/bin/activate
|
72
|
+
|
73
|
+
# Install agentstr
|
74
|
+
pip install --upgrade pip
|
75
|
+
pip install agentstr
|
76
|
+
```
|
77
|
+
|
78
|
+
## Examples
|
79
|
+
|
80
|
+
See our [examples directory](examples/) for complete working implementations:
|
81
|
+
|
82
|
+
- [Basic CLI Agent](examples/basic_cli/main.py) - A complete example showing:
|
83
|
+
- Setting up merchant profiles
|
84
|
+
- Creating stalls with shipping methods
|
85
|
+
- Defining products with shipping costs
|
86
|
+
- Configuring the agent with the merchant toolkit
|
87
|
+
- Running an interactive CLI application
|
88
|
+
|
89
|
+
|
90
|
+
## Documentation
|
91
|
+
|
92
|
+
For more detailed documentation and examples, see [Docs](docs/docs.md)
|
93
|
+
|
94
|
+
## Development
|
95
|
+
|
96
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for:
|
97
|
+
- Development setup
|
98
|
+
- Testing instructions
|
99
|
+
- Contribution guidelines
|
100
|
+
|
101
|
+
## License
|
102
|
+
|
103
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
104
|
+
|
105
|
+
## Acknowledgments
|
106
|
+
|
107
|
+
- [Phidata](https://www.phidata.com) - For their AI agent framework
|
108
|
+
- [Rust-Nostr](https://rust-nostr.org) - For their Python Nostr SDK
|
109
|
+
- [Nostr Protocol](https://github.com/nostr-protocol/nips) - For the protocol specification
|
110
|
+
|
@@ -0,0 +1,8 @@
|
|
1
|
+
agentstr/__init__.py,sha256=bPXCN4fDtqII9UtDCwhWhkR6bi1LR4w0rR0vGeKPNoI,567
|
2
|
+
agentstr/marketplace.py,sha256=CavX0WQaCiz1sQhVs-PaHZ4YYUdcabW5V5eXrhtbT5A,40406
|
3
|
+
agentstr/nostr.py,sha256=PId6477VuShPq7nKgansgyJhJNNy9S8ycCf_3niizg4,11242
|
4
|
+
agentstr-0.1.8.dist-info/LICENSE,sha256=20H0yoEDN5XO1xPXyZCyJjvSTP0YiarRMKWPfiaBhQY,1063
|
5
|
+
agentstr-0.1.8.dist-info/METADATA,sha256=iHwN1hOoezYlYtYLAETT1evku9GAOzWthol7K7roYfs,3376
|
6
|
+
agentstr-0.1.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
7
|
+
agentstr-0.1.8.dist-info/top_level.txt,sha256=KZObFRHppZvKUGYB_m9w5HhLwps7jj7w6Xrw73dH2ss,9
|
8
|
+
agentstr-0.1.8.dist-info/RECORD,,
|
@@ -1,111 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.2
|
2
|
-
Name: agentstr
|
3
|
-
Version: 0.1.7
|
4
|
-
Summary: A library for collaborative AI agents
|
5
|
-
Author-email: Alejandro Gil <info@synvya.com>
|
6
|
-
License: MIT
|
7
|
-
Project-URL: Homepage, https://github.com/synvya/agentstr
|
8
|
-
Project-URL: Documentation, https://github.com/synvya/agentstr#readme
|
9
|
-
Project-URL: BugTracker, https://github.com/synvya/agentstr/issues
|
10
|
-
Keywords: AI,agents,collaboration,library
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
13
|
-
Classifier: Operating System :: OS Independent
|
14
|
-
Requires-Python: <3.13,>=3.9
|
15
|
-
Description-Content-Type: text/markdown
|
16
|
-
License-File: LICENSE
|
17
|
-
Requires-Dist: phidata>=2.7.0
|
18
|
-
Requires-Dist: openai>=1.50.0
|
19
|
-
Requires-Dist: packaging>=24.0
|
20
|
-
Requires-Dist: nostr-sdk>=0.38.0
|
21
|
-
|
22
|
-
AgentStr
|
23
|
-
========
|
24
|
-
AgentStr is an extension of [Phidata](https://www.phidata.com) AI agents that allows for agents to communicate with other agents in separate computers using the Nostr communication protocol.
|
25
|
-
|
26
|
-
The goal is for Agent A operated by Company A to be able to work with Agent B operated by Company B to achieve a common goal. For example: Company A wants to buy a product sold by Company B so Agent A and Agent B can coordinate and execute the transaction.
|
27
|
-
|
28
|
-
The basic communication tools are implemented in `agentstr/nostr.py`.
|
29
|
-
|
30
|
-
As a first example, AgentStr provides the tools to create and operate a marketplace using the [NIP-15](https://github.com/nostr-protocol/nips/blob/master/15.md) Nostr Marketplace as its foundation. The file `agentstr/marketplace.py` includes NIP-15 `merchant` and `customer` profiles implemented each as a Phidata Toolkit.
|
31
|
-
|
32
|
-
# License
|
33
|
-
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
34
|
-
|
35
|
-
# Current status
|
36
|
-
The library is in its infancy.
|
37
|
-
|
38
|
-
Done:
|
39
|
-
- Workflow to package and distribute the library
|
40
|
-
- Users can create a Merchant profile and create an agent with the `merchant` toolkit that acts on behalf of the Merchant profile
|
41
|
-
|
42
|
-
|
43
|
-
To be done:
|
44
|
-
- Create a `marketplace` with `stalls`
|
45
|
-
- Merchants to define `products`
|
46
|
-
- Create a `customer` Toolkit
|
47
|
-
|
48
|
-
# Installation
|
49
|
-
AgentStr is offered as a python library available at https://pypi.org/project/agentstr/.
|
50
|
-
|
51
|
-
Here is an example on how to use the library:
|
52
|
-
|
53
|
-
1. Create a new python environment for your app
|
54
|
-
```
|
55
|
-
cd ~/
|
56
|
-
python3 -m venv ~/.venvs/aienv
|
57
|
-
source ~/.venvs/aienv/bin/activate
|
58
|
-
```
|
59
|
-
2. Install the agentstr library
|
60
|
-
```
|
61
|
-
pip install --upgrade pip
|
62
|
-
pip install agentstr
|
63
|
-
mkdir ~/mysampleapp
|
64
|
-
cd ~/mysampleapp
|
65
|
-
```
|
66
|
-
3. Create a new python file
|
67
|
-
```
|
68
|
-
touch main.py
|
69
|
-
```
|
70
|
-
4. Copy paste this code to the main.py file
|
71
|
-
```
|
72
|
-
from dotenv import load_dotenv
|
73
|
-
from os import getenv
|
74
|
-
from phi.agent import Agent
|
75
|
-
from phi.model.openai import OpenAIChat
|
76
|
-
from agentstr.marketplace import MerchantProfile, Merchant
|
77
|
-
|
78
|
-
|
79
|
-
profile = MerchantProfile(
|
80
|
-
"Synvya",
|
81
|
-
"Testing stuff",
|
82
|
-
"https://i.nostr.build/ocjZ5GlAKwrvgRhx.png",
|
83
|
-
getenv("NSEC_KEY")
|
84
|
-
)
|
85
|
-
|
86
|
-
agent = Agent(
|
87
|
-
name="Merchant Assistant",
|
88
|
-
model=OpenAIChat(id="gpt-4o"),
|
89
|
-
tools=[Merchant(merchant_profile=profile, relay="wss://relay.damus.io")],
|
90
|
-
show_tool_calls=True,
|
91
|
-
markdown=True,
|
92
|
-
debug_mode=True
|
93
|
-
)
|
94
|
-
|
95
|
-
agent.print_response("Publish the merchant information and tell me full URL where I can find it")
|
96
|
-
```
|
97
|
-
5. Export your OpenAI key and optionally a Nostr private key before running the code
|
98
|
-
```
|
99
|
-
export OPENAI_API_KEY="sk-***"
|
100
|
-
export NSEC_KEY="nsec***"
|
101
|
-
python main.py
|
102
|
-
```
|
103
|
-
|
104
|
-
This example will attempt to load a Nostr private key defined as NSEC_KEY in bech32 format. If a private key is not provided, the `MerchantProfile` class initializer will assign it a new one.
|
105
|
-
|
106
|
-
# Contributing
|
107
|
-
Refer to [CONTRIBUTING.md](CONTRIBUTING.md) for specific instructions on installation instructions for developers and how to contribute.
|
108
|
-
|
109
|
-
# Acknowledgments
|
110
|
-
- [Phidata](https://www.phidata.com) - For building robust AI agents.
|
111
|
-
- [Rust-Nostr](https://rust-nostr.org/index.html) - For providing a python based Nostr SDK.
|
agentstr-0.1.7.dist-info/RECORD
DELETED
@@ -1,8 +0,0 @@
|
|
1
|
-
agentstr/__init__.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
|
2
|
-
agentstr/marketplace.py,sha256=XYF2ZrF6MpmxgCEOz3uN436cj7F4VHyUATFk8gmr7Zg,5245
|
3
|
-
agentstr/nostr.py,sha256=C5zMWKBbXLkarZxBoTydJCZVzqGoCogBd_9v8Gll0VU,5012
|
4
|
-
agentstr-0.1.7.dist-info/LICENSE,sha256=xF8akIKB07gOtkhjENT0xVbWGdFp4-srDKpZKjD03Js,1063
|
5
|
-
agentstr-0.1.7.dist-info/METADATA,sha256=gz4B0qomWjGErWCDqZi2B5j4rEpa0mT7kY03WLThGXg,4092
|
6
|
-
agentstr-0.1.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
7
|
-
agentstr-0.1.7.dist-info/top_level.txt,sha256=KZObFRHppZvKUGYB_m9w5HhLwps7jj7w6Xrw73dH2ss,9
|
8
|
-
agentstr-0.1.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|