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