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.
- agentstr-0.1.12/MANIFEST.in +3 -0
- agentstr-0.1.12/PKG-INFO +129 -0
- agentstr-0.1.12/README.md +101 -0
- {agentstr-0.1.10 → agentstr-0.1.12}/pyproject.toml +24 -13
- agentstr-0.1.12/src/agentstr/__init__.py +37 -0
- agentstr-0.1.12/src/agentstr/buyer.py +291 -0
- agentstr-0.1.12/src/agentstr/buyer.pyi +31 -0
- agentstr-0.1.10/src/agentstr/marketplace.py → agentstr-0.1.12/src/agentstr/merchant.py +126 -323
- agentstr-0.1.12/src/agentstr/merchant.pyi +37 -0
- agentstr-0.1.12/src/agentstr/models.py +381 -0
- agentstr-0.1.12/src/agentstr/models.pyi +103 -0
- agentstr-0.1.12/src/agentstr/nostr.py +663 -0
- agentstr-0.1.12/src/agentstr/nostr.pyi +82 -0
- agentstr-0.1.12/src/agentstr/py.typed +0 -0
- agentstr-0.1.12/src/agentstr.egg-info/PKG-INFO +129 -0
- {agentstr-0.1.10 → agentstr-0.1.12}/src/agentstr.egg-info/SOURCES.txt +11 -5
- {agentstr-0.1.10 → agentstr-0.1.12}/src/agentstr.egg-info/requires.txt +5 -2
- agentstr-0.1.12/tests/test_buyer.py +91 -0
- agentstr-0.1.12/tests/test_merchant.py +138 -0
- agentstr-0.1.12/tests/test_nostr_integration.py +94 -0
- agentstr-0.1.12/tests/test_nostr_mocked.py +101 -0
- agentstr-0.1.10/MANIFEST.in +0 -5
- agentstr-0.1.10/PKG-INFO +0 -133
- agentstr-0.1.10/README.md +0 -88
- agentstr-0.1.10/src/agentstr/__init__.py +0 -30
- agentstr-0.1.10/src/agentstr/examples/basic_cli/.env.example +0 -2
- agentstr-0.1.10/src/agentstr/examples/basic_cli/README.md +0 -11
- agentstr-0.1.10/src/agentstr/examples/basic_cli/main.py +0 -193
- agentstr-0.1.10/src/agentstr/nostr.py +0 -327
- agentstr-0.1.10/src/agentstr.egg-info/PKG-INFO +0 -133
- agentstr-0.1.10/tests/test_merchant.py +0 -371
- agentstr-0.1.10/tests/test_nostr.py +0 -164
- {agentstr-0.1.10 → agentstr-0.1.12}/LICENSE +0 -0
- {agentstr-0.1.10 → agentstr-0.1.12}/setup.cfg +0 -0
- {agentstr-0.1.10 → agentstr-0.1.12}/src/agentstr.egg-info/dependency_links.txt +0 -0
- {agentstr-0.1.10 → agentstr-0.1.12}/src/agentstr.egg-info/top_level.txt +0 -0
agentstr-0.1.12/PKG-INFO
ADDED
@@ -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.
|
8
|
-
description = "Nostr extension for
|
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 = {
|
11
|
+
license = { text = "MIT" }
|
12
12
|
authors = [
|
13
|
-
{name = "Synvya", email = "info@synvya.com"}
|
13
|
+
{name = "Synvya", email = "info@synvya.com"}
|
14
14
|
]
|
15
15
|
dependencies = [
|
16
|
-
"
|
16
|
+
"agno>=1.1.1",
|
17
17
|
"openai>=1.50.0",
|
18
18
|
"packaging>=24.0",
|
19
|
-
"
|
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://
|
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: ...
|