agentstr 0.1.10__tar.gz → 0.1.12__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.
- 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: ...
|