agentstr 0.1.10__tar.gz → 0.1.12__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. agentstr-0.1.12/MANIFEST.in +3 -0
  2. agentstr-0.1.12/PKG-INFO +129 -0
  3. agentstr-0.1.12/README.md +101 -0
  4. {agentstr-0.1.10 → agentstr-0.1.12}/pyproject.toml +24 -13
  5. agentstr-0.1.12/src/agentstr/__init__.py +37 -0
  6. agentstr-0.1.12/src/agentstr/buyer.py +291 -0
  7. agentstr-0.1.12/src/agentstr/buyer.pyi +31 -0
  8. agentstr-0.1.10/src/agentstr/marketplace.py → agentstr-0.1.12/src/agentstr/merchant.py +126 -323
  9. agentstr-0.1.12/src/agentstr/merchant.pyi +37 -0
  10. agentstr-0.1.12/src/agentstr/models.py +381 -0
  11. agentstr-0.1.12/src/agentstr/models.pyi +103 -0
  12. agentstr-0.1.12/src/agentstr/nostr.py +663 -0
  13. agentstr-0.1.12/src/agentstr/nostr.pyi +82 -0
  14. agentstr-0.1.12/src/agentstr/py.typed +0 -0
  15. agentstr-0.1.12/src/agentstr.egg-info/PKG-INFO +129 -0
  16. {agentstr-0.1.10 → agentstr-0.1.12}/src/agentstr.egg-info/SOURCES.txt +11 -5
  17. {agentstr-0.1.10 → agentstr-0.1.12}/src/agentstr.egg-info/requires.txt +5 -2
  18. agentstr-0.1.12/tests/test_buyer.py +91 -0
  19. agentstr-0.1.12/tests/test_merchant.py +138 -0
  20. agentstr-0.1.12/tests/test_nostr_integration.py +94 -0
  21. agentstr-0.1.12/tests/test_nostr_mocked.py +101 -0
  22. agentstr-0.1.10/MANIFEST.in +0 -5
  23. agentstr-0.1.10/PKG-INFO +0 -133
  24. agentstr-0.1.10/README.md +0 -88
  25. agentstr-0.1.10/src/agentstr/__init__.py +0 -30
  26. agentstr-0.1.10/src/agentstr/examples/basic_cli/.env.example +0 -2
  27. agentstr-0.1.10/src/agentstr/examples/basic_cli/README.md +0 -11
  28. agentstr-0.1.10/src/agentstr/examples/basic_cli/main.py +0 -193
  29. agentstr-0.1.10/src/agentstr/nostr.py +0 -327
  30. agentstr-0.1.10/src/agentstr.egg-info/PKG-INFO +0 -133
  31. agentstr-0.1.10/tests/test_merchant.py +0 -371
  32. agentstr-0.1.10/tests/test_nostr.py +0 -164
  33. {agentstr-0.1.10 → agentstr-0.1.12}/LICENSE +0 -0
  34. {agentstr-0.1.10 → agentstr-0.1.12}/setup.cfg +0 -0
  35. {agentstr-0.1.10 → agentstr-0.1.12}/src/agentstr.egg-info/dependency_links.txt +0 -0
  36. {agentstr-0.1.10 → agentstr-0.1.12}/src/agentstr.egg-info/top_level.txt +0 -0
@@ -0,0 +1,3 @@
1
+ include LICENSE README.md
2
+ global-exclude __pycache__
3
+ global-exclude *.py[cod]
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.2
2
+ Name: agentstr
3
+ Version: 0.1.12
4
+ Summary: Nostr extension for Agno AI agents
5
+ Author-email: Synvya <info@synvya.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://www.synvya.com
8
+ Project-URL: Repository, https://github.com/synvya/agentstr
9
+ Project-URL: Documentation, https://github.com/synvya/agentstr#readme
10
+ Project-URL: BugTracker, https://github.com/synvya/agentstr/issues
11
+ Requires-Python: <3.13,>=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: agno>=1.1.1
15
+ Requires-Dist: openai>=1.50.0
16
+ Requires-Dist: packaging>=24.0
17
+ Requires-Dist: nostr_sdk>=0.39.0
18
+ Requires-Dist: pydantic>=2.0.0
19
+ Requires-Dist: cassandra-driver>=3.29.2
20
+ Requires-Dist: cassio>=0.1.10
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.0; extra == "dev"
23
+ Requires-Dist: pytest-asyncio>=0.23.5; extra == "dev"
24
+ Requires-Dist: black>=23.0; extra == "dev"
25
+ Requires-Dist: isort>=5.0; extra == "dev"
26
+ Requires-Dist: mypy>=1.0; extra == "dev"
27
+ Requires-Dist: python-dotenv>=1.0; extra == "dev"
28
+
29
+ # AgentStr
30
+
31
+ AgentStr is an extension of [Agno](https://www.agno.ai) AI agents that enables peer-to-peer agent communication using the Nostr protocol.
32
+
33
+ ## Overview
34
+
35
+ AgentStr allows AI agents operated by different organizations to communicate and collaborate. For example:
36
+ - Agent A from Company A can coordinate with Agent B from Company B to execute a transaction
37
+ - Agents can discover and interact with each other through the decentralized Nostr network
38
+ - No central authority or intermediary required
39
+
40
+ ## Project Structure
41
+
42
+ ```
43
+ agentstr/
44
+ ├── src/ # Source code
45
+ │ └── agentstr/
46
+ │ ├── __init__.py
47
+ │ ├── buyer.py
48
+ │ ├── buyer.pyi
49
+ │ ├── merchant.py
50
+ │ ├── merchant.pyi
51
+ │ ├── models.py
52
+ │ ├── models.pyi
53
+ │ ├── nostr.py
54
+ │ ├── nostr.pyi
55
+ │ └── py.typed
56
+ ├── tests/ # Test files
57
+ ├── docs/ # Documentation
58
+ ├── examples/ # Example implementations
59
+ └── ...
60
+ ```
61
+
62
+ ## Features
63
+
64
+ ### Current Features
65
+ - Create Merchant agents with Nostr identities:
66
+ - Publish and manage merchant products using [NIP-15](https://github.com/nostr-protocol/nips/blob/master/15.md) marketplace protocol
67
+ - Create merchant stalls to organize products
68
+ - Handle shipping zones and costs
69
+ - Secure communication using Nostr keys
70
+ - Create Buyer agents:
71
+ - Retrieve a list of sellers from the relay using [NIP-15](https://github.com/nostr-protocol/nips/blob/master/15.md) marketplace protocol
72
+ - Find an specific seller by name or public key
73
+ - Refresh the list of sellers from the relay
74
+
75
+ ### Roadmap
76
+ - [ ] Create marketplace with stalls
77
+ - [ ] Expand buyer agent to include more features
78
+ - [ ] Support additional Nostr NIPs
79
+ - [ ] Add more agent interaction patterns
80
+
81
+ ## Installation
82
+
83
+ ```bash
84
+ # Create a new python environment
85
+ python3 -m venv ~/.venvs/aienv
86
+ source ~/.venvs/aienv/bin/activate
87
+
88
+ # Install agentstr
89
+ pip install --upgrade pip
90
+ pip install agentstr
91
+ ```
92
+
93
+ ## Examples
94
+
95
+ You can find example code in the [examples](https://github.com/Synvya/agentstr/tree/main/examples/) directory.
96
+
97
+ To install the examples clone the repository and navigate to the examples directory:
98
+
99
+ ```bash
100
+ git clone https://github.com/Synvya/agentstr.git
101
+ cd agentstr/examples/
102
+ ```
103
+ Each example has its own README with instructions on how to run it.
104
+
105
+ ## Documentation
106
+
107
+ For more detailed documentation and examples, see [Docs](https://github.com/Synvya/agentstr/tree/main/docs/docs.md)
108
+
109
+ ## Development
110
+
111
+ See [CONTRIBUTING.md](https://github.com/Synvya/agentstr/blob/main/CONTRIBUTING.md) for:
112
+ - Development setup
113
+ - Testing instructions
114
+ - Contribution guidelines
115
+
116
+ ## License
117
+
118
+ This project is licensed under the MIT License - see the [LICENSE](https://github.com/Synvya/agentstr/blob/main/LICENSE) file for details.
119
+
120
+ ## Acknowledgments
121
+
122
+ - [Agno](https://www.agno.ai) - For their AI agent framework
123
+ - [Rust-Nostr](https://rust-nostr.org) - For their Python Nostr SDK
124
+ - [Nostr Protocol](https://github.com/nostr-protocol/nips) - For the protocol specification
125
+
126
+ This software includes the following software licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0):
127
+ - [DataStax Python Driver for Apache Cassandra](https://github.com/datastax/python-driver)
128
+ - [cassIO](https://github.com/CassioML/cassio). This library is not maintained anymore. We will need to replace it with a new library.
129
+
@@ -0,0 +1,101 @@
1
+ # AgentStr
2
+
3
+ AgentStr is an extension of [Agno](https://www.agno.ai) AI agents that enables peer-to-peer agent communication using the Nostr protocol.
4
+
5
+ ## Overview
6
+
7
+ AgentStr allows AI agents operated by different organizations to communicate and collaborate. For example:
8
+ - Agent A from Company A can coordinate with Agent B from Company B to execute a transaction
9
+ - Agents can discover and interact with each other through the decentralized Nostr network
10
+ - No central authority or intermediary required
11
+
12
+ ## Project Structure
13
+
14
+ ```
15
+ agentstr/
16
+ ├── src/ # Source code
17
+ │ └── agentstr/
18
+ │ ├── __init__.py
19
+ │ ├── buyer.py
20
+ │ ├── buyer.pyi
21
+ │ ├── merchant.py
22
+ │ ├── merchant.pyi
23
+ │ ├── models.py
24
+ │ ├── models.pyi
25
+ │ ├── nostr.py
26
+ │ ├── nostr.pyi
27
+ │ └── py.typed
28
+ ├── tests/ # Test files
29
+ ├── docs/ # Documentation
30
+ ├── examples/ # Example implementations
31
+ └── ...
32
+ ```
33
+
34
+ ## Features
35
+
36
+ ### Current Features
37
+ - Create Merchant agents with Nostr identities:
38
+ - Publish and manage merchant products using [NIP-15](https://github.com/nostr-protocol/nips/blob/master/15.md) marketplace protocol
39
+ - Create merchant stalls to organize products
40
+ - Handle shipping zones and costs
41
+ - Secure communication using Nostr keys
42
+ - Create Buyer agents:
43
+ - Retrieve a list of sellers from the relay using [NIP-15](https://github.com/nostr-protocol/nips/blob/master/15.md) marketplace protocol
44
+ - Find an specific seller by name or public key
45
+ - Refresh the list of sellers from the relay
46
+
47
+ ### Roadmap
48
+ - [ ] Create marketplace with stalls
49
+ - [ ] Expand buyer agent to include more features
50
+ - [ ] Support additional Nostr NIPs
51
+ - [ ] Add more agent interaction patterns
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ # Create a new python environment
57
+ python3 -m venv ~/.venvs/aienv
58
+ source ~/.venvs/aienv/bin/activate
59
+
60
+ # Install agentstr
61
+ pip install --upgrade pip
62
+ pip install agentstr
63
+ ```
64
+
65
+ ## Examples
66
+
67
+ You can find example code in the [examples](https://github.com/Synvya/agentstr/tree/main/examples/) directory.
68
+
69
+ To install the examples clone the repository and navigate to the examples directory:
70
+
71
+ ```bash
72
+ git clone https://github.com/Synvya/agentstr.git
73
+ cd agentstr/examples/
74
+ ```
75
+ Each example has its own README with instructions on how to run it.
76
+
77
+ ## Documentation
78
+
79
+ For more detailed documentation and examples, see [Docs](https://github.com/Synvya/agentstr/tree/main/docs/docs.md)
80
+
81
+ ## Development
82
+
83
+ See [CONTRIBUTING.md](https://github.com/Synvya/agentstr/blob/main/CONTRIBUTING.md) for:
84
+ - Development setup
85
+ - Testing instructions
86
+ - Contribution guidelines
87
+
88
+ ## License
89
+
90
+ This project is licensed under the MIT License - see the [LICENSE](https://github.com/Synvya/agentstr/blob/main/LICENSE) file for details.
91
+
92
+ ## Acknowledgments
93
+
94
+ - [Agno](https://www.agno.ai) - For their AI agent framework
95
+ - [Rust-Nostr](https://rust-nostr.org) - For their Python Nostr SDK
96
+ - [Nostr Protocol](https://github.com/nostr-protocol/nips) - For the protocol specification
97
+
98
+ This software includes the following software licensed under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0):
99
+ - [DataStax Python Driver for Apache Cassandra](https://github.com/datastax/python-driver)
100
+ - [cassIO](https://github.com/CassioML/cassio). This library is not maintained anymore. We will need to replace it with a new library.
101
+
@@ -4,25 +4,28 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentstr"
7
- version = "0.1.10"
8
- description = "Nostr extension for Phidata AI agents"
7
+ version = "0.1.12"
8
+ description = "Nostr extension for Agno AI agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9, <3.13"
11
- license = {file = "LICENSE"}
11
+ license = { text = "MIT" }
12
12
  authors = [
13
- {name = "Synvya", email = "info@synvya.com"} # Add author info
13
+ {name = "Synvya", email = "info@synvya.com"}
14
14
  ]
15
15
  dependencies = [
16
- "phidata>=2.7.0",
16
+ "agno>=1.1.1",
17
17
  "openai>=1.50.0",
18
18
  "packaging>=24.0",
19
- "nostr-sdk>=0.38.0",
19
+ "nostr_sdk>=0.39.0",
20
20
  "pydantic>=2.0.0",
21
+ "cassandra-driver>=3.29.2",
22
+ "cassio>=0.1.10",
21
23
  ]
22
24
 
23
25
  [project.optional-dependencies]
24
26
  dev = [
25
27
  "pytest>=7.0",
28
+ "pytest-asyncio>=0.23.5",
26
29
  "black>=23.0",
27
30
  "isort>=5.0",
28
31
  "mypy>=1.0",
@@ -30,17 +33,14 @@ dev = [
30
33
  ]
31
34
 
32
35
  [project.urls]
33
- Homepage = "https://github.com/synvya/agentstr"
36
+ Homepage = "https://www.synvya.com"
37
+ Repository = "https://github.com/synvya/agentstr"
34
38
  Documentation = "https://github.com/synvya/agentstr#readme"
35
39
  BugTracker = "https://github.com/synvya/agentstr/issues"
36
40
 
37
41
  [tool.setuptools]
38
42
  package-dir = {"" = "src"}
39
- packages = [
40
- "agentstr",
41
- "agentstr.examples",
42
- "agentstr.examples.basic_cli"
43
- ]
43
+ packages = ["agentstr"]
44
44
 
45
45
  [tool.black]
46
46
  line-length = 88
@@ -58,7 +58,18 @@ python_version = "3.9"
58
58
  warn_return_any = true
59
59
  warn_unused_configs = true
60
60
  mypy_path = "src"
61
+ check_untyped_defs = true
62
+ disallow_untyped_defs = true
61
63
 
62
64
  [[tool.mypy.overrides]]
63
65
  module = ["nostr_sdk.*"]
64
- ignore_missing_imports = true
66
+ ignore_missing_imports = true
67
+
68
+ [tool.setuptools.package-data]
69
+ agentstr = ["py.typed"]
70
+
71
+ [tool.pytest.ini_options]
72
+ asyncio_mode = "auto"
73
+ markers = [
74
+ "asyncio: mark test as async",
75
+ ]
@@ -0,0 +1,37 @@
1
+ """
2
+ AgentStr: Nostr extension for Agno AI agents
3
+ """
4
+
5
+ from nostr_sdk import ShippingCost, ShippingMethod # type: ignore
6
+
7
+ from .merchant import MerchantTools
8
+
9
+ # Import main classes to make them available at package level
10
+ from .models import AgentProfile, MerchantProduct, MerchantStall, NostrProfile
11
+
12
+ # Import version from pyproject.toml at runtime
13
+ try:
14
+ from importlib.metadata import version
15
+
16
+ __version__ = version("agentstr")
17
+ except Exception:
18
+ __version__ = "unknown"
19
+
20
+ __all__ = [
21
+ "MerchantTools",
22
+ "MerchantProduct",
23
+ "MerchantStall",
24
+ "ShippingCost",
25
+ "ShippingMethod",
26
+ ]
27
+
28
+ from agentstr.nostr import EventId, Keys, NostrClient, ProductData, StallData
29
+
30
+ __all__ = [
31
+ "EventId",
32
+ "Keys",
33
+ "NostrClient",
34
+ "ProductData",
35
+ "StallData",
36
+ "AgentProfile",
37
+ ]
@@ -0,0 +1,291 @@
1
+ import json
2
+ import logging
3
+ from uuid import uuid4
4
+
5
+ from agno.agent import AgentKnowledge # type: ignore
6
+ from agno.document.base import Document
7
+
8
+ from agentstr.models import AgentProfile, NostrProfile
9
+ from agentstr.nostr import NostrClient, PublicKey
10
+
11
+ try:
12
+ from agno.tools import Toolkit
13
+ except ImportError:
14
+ raise ImportError("`agno` not installed. Please install using `pip install agno`")
15
+
16
+
17
+ def _map_location_to_geohash(location: str) -> str:
18
+ """
19
+ Map a location to a geohash.
20
+
21
+ TBD: Implement this function. Returning a fixed geohash for now.
22
+
23
+ Args:
24
+ location: location to map to a geohash. Can be a zip code, city, state, country, or latitude and longitude.
25
+
26
+ Returns:
27
+ str: geohash of the location or empty string if location is not found
28
+ """
29
+ if "snoqualmie" in location.lower():
30
+ return "C23Q7U36W"
31
+ else:
32
+ return ""
33
+
34
+
35
+ class BuyerTools(Toolkit):
36
+ """
37
+ BuyerTools is a toolkit that allows an agent to find sellers and transact with them over Nostr.
38
+
39
+ Sellers are downloaded from the Nostr relay and cached.
40
+ Sellers can be found by name or public key.
41
+ Sellers cache can be refreshed from the Nostr relay.
42
+ Sellers can be retrieved as a list of Nostr profiles.
43
+
44
+ TBD: populate the sellers locations with info from stalls.
45
+ """
46
+
47
+ from pydantic import ConfigDict
48
+
49
+ model_config = ConfigDict(
50
+ arbitrary_types_allowed=True, extra="allow", validate_assignment=True
51
+ )
52
+
53
+ logger = logging.getLogger("Buyer")
54
+ sellers: set[NostrProfile] = set()
55
+
56
+ def __init__(
57
+ self,
58
+ knowledge_base: AgentKnowledge,
59
+ buyer_profile: AgentProfile,
60
+ relay: str,
61
+ ) -> None:
62
+ """Initialize the Buyer toolkit.
63
+
64
+ Args:
65
+ knowledge_base: knowledge base of the buyer agent
66
+ buyer_profile: profile of the buyer using this agent
67
+ relay: Nostr relay to use for communications
68
+ """
69
+ super().__init__(name="Buyer")
70
+
71
+ self.relay = relay
72
+ self.buyer_profile = buyer_profile
73
+ self.knowledge_base = knowledge_base
74
+ # Initialize fields
75
+ self._nostr_client = NostrClient(relay, buyer_profile.get_private_key())
76
+
77
+ # Register methods
78
+ self.register(self.find_seller_by_name)
79
+ self.register(self.find_seller_by_public_key)
80
+ self.register(self.find_sellers_by_location)
81
+ self.register(self.get_profile)
82
+ self.register(self.get_relay)
83
+ self.register(self.get_seller_stalls)
84
+ self.register(self.get_seller_products)
85
+ self.register(self.get_seller_count)
86
+ self.register(self.get_sellers)
87
+ self.register(self.refresh_sellers)
88
+ self.register(self.purchase_product)
89
+
90
+ def purchase_product(self, product: str) -> str:
91
+ """Purchase a product.
92
+
93
+ Args:
94
+ product: JSON string with product to purchase
95
+ """
96
+ return json.dumps({"status": "success", "message": "Product purchased"})
97
+
98
+ def find_seller_by_name(self, name: str) -> str:
99
+ """Find a seller by name.
100
+
101
+ Args:
102
+ name: name of the seller to find
103
+
104
+ Returns:
105
+ str: JSON string with seller profile or error message
106
+ """
107
+ for seller in self.sellers:
108
+ if seller.get_name() == name:
109
+ response = seller.to_json()
110
+ # self._store_response_in_knowledge_base(response)
111
+ return response
112
+ response = json.dumps({"status": "error", "message": "Seller not found"})
113
+ self._store_response_in_knowledge_base(response)
114
+ return response
115
+
116
+ def find_seller_by_public_key(self, public_key: str) -> str:
117
+ """Find a seller by public key.
118
+
119
+ Args:
120
+ public_key: bech32 encoded public key of the seller to find
121
+
122
+ Returns:
123
+ str: seller profile json string or error message
124
+ """
125
+ for seller in self.sellers:
126
+ if seller.get_public_key() == public_key:
127
+ response = seller.to_json()
128
+ # self._store_response_in_knowledge_base(response)
129
+ return response
130
+ response = json.dumps({"status": "error", "message": "Seller not found"})
131
+ self._store_response_in_knowledge_base(response)
132
+ return response
133
+
134
+ def find_sellers_by_location(self, location: str) -> str:
135
+ """Find sellers by location.
136
+
137
+ Args:
138
+ location: location of the seller to find (e.g. "San Francisco, CA")
139
+
140
+ Returns:
141
+ str: list of seller profile json strings or error message
142
+ """
143
+ sellers: set[NostrProfile] = set()
144
+ geohash = _map_location_to_geohash(location)
145
+ # print(f"find_sellers_by_location: geohash: {geohash}")
146
+
147
+ if not geohash:
148
+ response = json.dumps({"status": "error", "message": "Invalid location"})
149
+ return response
150
+
151
+ # Find sellers in the same geohash
152
+ for seller in self.sellers:
153
+ if geohash in seller.get_locations():
154
+ # print(
155
+ # f"geohash {geohash} found in seller {seller.get_name()} with locations {seller.get_locations()}"
156
+ # )
157
+ sellers.add(seller)
158
+
159
+ if not sellers:
160
+ response = json.dumps(
161
+ {"status": "error", "message": f"No sellers found near {location}"}
162
+ )
163
+ return response
164
+
165
+ response = json.dumps([seller.to_dict() for seller in sellers])
166
+ # print("find_sellers_by_location: storing response in knowledge base")
167
+ self._store_response_in_knowledge_base(response)
168
+ # print(f"Found {len(sellers)} sellers near {location}")
169
+ return response
170
+
171
+ def get_profile(self) -> str:
172
+ """Get the Nostr profile of the buyer agent.
173
+
174
+ Returns:
175
+ str: buyer profile json string
176
+ """
177
+ response = self.buyer_profile.to_json()
178
+ self._store_response_in_knowledge_base(response)
179
+ return response
180
+
181
+ def get_relay(self) -> str:
182
+ """Get the Nostr relay that the buyer agent is using.
183
+
184
+ Returns:
185
+ str: Nostr relay
186
+ """
187
+ response = self.relay
188
+ # self._store_response_in_knowledge_base(response)
189
+ return response
190
+
191
+ def get_seller_stalls(self, public_key: str) -> str:
192
+ """Get the stalls from a seller.
193
+
194
+ Args:
195
+ public_key: public key of the seller
196
+
197
+ Returns:
198
+ str: JSON string with seller collections
199
+ """
200
+ try:
201
+ stalls = self._nostr_client.retrieve_stalls_from_seller(
202
+ PublicKey.parse(public_key)
203
+ )
204
+ response = json.dumps([stall.as_json() for stall in stalls])
205
+ self._store_response_in_knowledge_base(response)
206
+ return response
207
+ except Exception as e:
208
+ response = json.dumps({"status": "error", "message": str(e)})
209
+ return response
210
+
211
+ def get_seller_count(self) -> str:
212
+ """Get the number of sellers.
213
+
214
+ Returns:
215
+ str: JSON string with status and count of sellers
216
+ """
217
+ response = json.dumps({"status": "success", "count": len(self.sellers)})
218
+ return response
219
+
220
+ def get_seller_products(self, public_key: str) -> str:
221
+ """Get the products from a seller
222
+
223
+ Args:
224
+ public_key: public key of the seller
225
+
226
+ Returns:
227
+ str: JSON string with seller products
228
+ """
229
+ try:
230
+ products = self._nostr_client.retrieve_products_from_seller(
231
+ PublicKey.parse(public_key)
232
+ )
233
+
234
+ response = json.dumps([product.to_dict() for product in products])
235
+ self._store_response_in_knowledge_base(response)
236
+ return response
237
+ except Exception as e:
238
+ response = json.dumps({"status": "error", "message": str(e)})
239
+ return response
240
+
241
+ def get_sellers(self) -> str:
242
+ """Get the list of sellers.
243
+ If no sellers are cached, the list is refreshed from the Nostr relay.
244
+ If sellers are cached, the list is returned from the cache.
245
+ To get a fresh list of sellers, call refresh_sellers() sellers first.
246
+
247
+ Returns:
248
+ str: list of sellers json strings
249
+ """
250
+ if not self.sellers:
251
+ self._refresh_sellers()
252
+ response = json.dumps([seller.to_json() for seller in self.sellers])
253
+ return response
254
+
255
+ def refresh_sellers(self) -> str:
256
+ """Refresh the list of sellers.
257
+
258
+ Returns:
259
+ str: JSON string with status and count of sellers refreshed
260
+ """
261
+ self._refresh_sellers()
262
+ response = json.dumps({"status": "success", "count": len(self.sellers)})
263
+ return response
264
+
265
+ def _refresh_sellers(self) -> None:
266
+ """
267
+ Internal fucntion to retrieve a new list of sellers from the Nostr relay.
268
+ The old list is discarded and the new list only contains unique sellers currently stored at the relay.
269
+
270
+ Returns:
271
+ List[NostrProfile]: List of Nostr profiles of all sellers.
272
+ """
273
+ sellers = self._nostr_client.retrieve_sellers()
274
+ if len(sellers) == 0:
275
+ self.logger.info("No sellers found")
276
+ else:
277
+ self.logger.info(f"Found {len(sellers)} sellers")
278
+
279
+ # Print the locations of the sellers
280
+ # for seller in sellers:
281
+ # print(f"Seller {seller.get_name()} has locations {seller.get_locations()}")
282
+
283
+ self.sellers = sellers
284
+
285
+ def _store_response_in_knowledge_base(self, response: str) -> None:
286
+ doc = Document(
287
+ id=str(uuid4()),
288
+ content=response,
289
+ )
290
+ # print(f"Document length: {len(doc.content.split())} words")
291
+ self.knowledge_base.load_documents([doc]) # Store response in Cassandra
@@ -0,0 +1,31 @@
1
+ from logging import Logger
2
+ from typing import ClassVar
3
+
4
+ from agno.agent import AgentKnowledge
5
+ from agno.tools import Toolkit
6
+
7
+ from agentstr.models import AgentProfile, NostrProfile
8
+ from agentstr.nostr import NostrClient
9
+
10
+ class BuyerTools(Toolkit):
11
+ logger: ClassVar[Logger]
12
+ sellers: set[NostrProfile]
13
+ relay: str
14
+ _nostr_client: NostrClient
15
+
16
+ def __init__(
17
+ self, knowledge_base: AgentKnowledge, buyer_profile: AgentProfile, relay: str
18
+ ) -> None: ...
19
+ def find_seller_by_name(self, name: str) -> str: ...
20
+ def find_seller_by_public_key(self, public_key: str) -> str: ...
21
+ def find_sellers_by_location(self, location: str) -> str: ...
22
+ def get_profile(self) -> str: ...
23
+ def get_relay(self) -> str: ...
24
+ def get_seller_stalls(self, public_key: str) -> str: ...
25
+ def get_seller_count(self) -> str: ...
26
+ def get_seller_products(self, public_key: str) -> str: ...
27
+ def get_sellers(self) -> str: ...
28
+ def purchase_product(self, product: str) -> str: ...
29
+ def refresh_sellers(self) -> str: ...
30
+ def _refresh_sellers(self) -> None: ...
31
+ def _store_response_in_knowledge_base(self, response: str) -> None: ...