agentstr 0.0.9__py3-none-any.whl → 0.0.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,163 @@
1
+ import logging
2
+ from typing import Optional
3
+ from . import nostr
4
+
5
+ try:
6
+ from phi.tools import Toolkit
7
+ except ImportError:
8
+ raise ImportError("`phidata` not installed. Please install using `pip install phidata`")
9
+
10
+ try:
11
+ import asyncio
12
+ except ImportError:
13
+ raise ImportError("`asyncio` not installed. Please install using `pip install asyncio`")
14
+
15
+ class MerchantProfile():
16
+
17
+ logger = logging.getLogger("MerchantProfile")
18
+
19
+ def __init__(
20
+ self,
21
+ name: str,
22
+ about: str,
23
+ picture: str,
24
+ nsec: Optional[str] = None
25
+ ):
26
+ """Initialize the Merchant profile.
27
+
28
+ Args:
29
+ name: Name for the merchant
30
+ about: brief description about the merchant
31
+ picture: url to a png file with a picture for the merchant
32
+ nsec: private key to be used by this Merchant
33
+ """
34
+
35
+ # Set log handling for MerchantProfile
36
+ if not MerchantProfile.logger.hasHandlers():
37
+ console_handler = logging.StreamHandler()
38
+ console_handler.setLevel(logging.INFO)
39
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
40
+ console_handler.setFormatter(formatter)
41
+ MerchantProfile.logger.addHandler(console_handler)
42
+
43
+ self.name = name
44
+ self.about = about
45
+ self.picture = picture
46
+
47
+ if nsec:
48
+ self.private_key = nsec
49
+ keys = nostr.Keys.parse(self.private_key)
50
+ self.public_key = keys.public_key().to_bech32()
51
+ MerchantProfile.logger.info(f"Pre-defined private key reused for {self.name}: {self.private_key}")
52
+ MerchantProfile.logger.info(f"Pre-defined public key reused for {self.name}: {self.public_key}")
53
+ else:
54
+ keys = nostr.Keys.generate()
55
+ self.private_key = keys.secret_key().to_bech32()
56
+ self.public_key = keys.public_key().to_bech32()
57
+ MerchantProfile.logger.info(f"New private key created for {self.name}: {self.private_key}")
58
+ MerchantProfile.logger.info(f"New public key created for {self.name}: {self.public_key}")
59
+
60
+ def merchant_profile_to_str(self) -> str:
61
+ return (
62
+ f"Merchant name: {self.name}. "
63
+ f"Merchant description: {self.about}. "
64
+ f"Merchant picture URL: {self.picture}. "
65
+ f"Private key: {self.private_key}. "
66
+ f"Public key: {self.public_key}."
67
+ )
68
+
69
+ def get_public_key(self) -> str:
70
+ return self.public_key
71
+
72
+ def get_private_key(self) -> str:
73
+ return self.private_key
74
+
75
+ def get_name(self) -> str:
76
+ return self.name
77
+
78
+ def get_about(self) -> str:
79
+ return self.about
80
+
81
+ def get_picture(self) -> str:
82
+ return self.picture
83
+
84
+
85
+
86
+ class Merchant(Toolkit):
87
+
88
+ WEB_URL: str = "http://njump.me/"
89
+
90
+ def __init__(
91
+ self,
92
+ merchant_profile: MerchantProfile,
93
+ relay: str,
94
+ ):
95
+ """Initialize the Merchant toolkit.
96
+
97
+ Args:
98
+ merchant_profile: profile of the merchant using this agent
99
+ relay: Nostr relay to use for communications
100
+ """
101
+ super().__init__(name="merchant")
102
+ self.relay = relay
103
+ self.merchant_profile = merchant_profile
104
+
105
+ # Register all methods
106
+ self.register(self.publish_merchant_profile)
107
+ self.register(self.get_merchant_url)
108
+
109
+ def publish_merchant_profile(
110
+ self
111
+ ) -> str:
112
+ """
113
+ Publishes the merchant profile on Nostr
114
+
115
+ Returns:
116
+ str: with event id and other details if successful or "error" string if unsuccesful
117
+ """
118
+ # Run the async pubilshing function synchronously
119
+ return asyncio.run(self._async_publish_merchant_profile())
120
+
121
+ async def _async_publish_merchant_profile(
122
+ self
123
+ ) -> str:
124
+ """
125
+ Asynchronous method to publish the merchant profile on Nostr
126
+
127
+ Returns:
128
+ str: with event id and other details if successful or "error" string if unsuccesful
129
+ """
130
+
131
+ nostr_client = nostr.NostrClient(self.relay, self.merchant_profile.get_private_key())
132
+
133
+ # Connect to the relay
134
+ outcome = await nostr_client.connect()
135
+
136
+ # Check if the operation resulted in an error
137
+ if outcome == nostr.NostrClient.ERROR:
138
+ return nostr.NostrClient.ERROR
139
+ else:
140
+ eventid = await nostr_client.publish_profile(
141
+ self.merchant_profile.get_name(),
142
+ self.merchant_profile.get_about(),
143
+ self.merchant_profile.get_picture()
144
+ )
145
+
146
+ # Check if the operation resulted in an error
147
+ if eventid == nostr.NostrClient.ERROR:
148
+ return nostr.NostrClient.ERROR
149
+
150
+ # Return the event ID and merchant profile details
151
+ return eventid + self.merchant_profile.merchant_profile_to_str()
152
+
153
+ def get_merchant_url(
154
+ self
155
+ ) -> str:
156
+ """
157
+ Returns URL with merchant profile
158
+
159
+ Returns:
160
+ str: valid URL with merchant profile
161
+ """
162
+
163
+ return self.WEB_URL + self.merchant_profile.get_public_key()
agentstr/nostr.py ADDED
@@ -0,0 +1,141 @@
1
+ from typing import Optional
2
+ from os import getenv
3
+ import logging
4
+
5
+ try:
6
+ import asyncio
7
+ except ImportError:
8
+ raise ImportError("`asyncio` not installed. Please install using `pip install asyncio`")
9
+
10
+ try:
11
+ from nostr_sdk import Keys, Client, EventBuilder, NostrSigner, SendEventOutput, Event, Metadata
12
+ except ImportError:
13
+ raise ImportError("`nostr_sdk` not installed. Please install using `pip install nostr_sdk`")
14
+
15
+ class NostrClient():
16
+
17
+ logger = logging.getLogger("NostrClient")
18
+ ERROR: str = "ERROR"
19
+ SUCCESS: str = "SUCCESS"
20
+
21
+ def __init__(
22
+ self,
23
+ relay: str = None,
24
+ nsec: str = None,
25
+ ):
26
+ """Initialize the Nostr client.
27
+
28
+ Args:
29
+ relay: Nostr relay that the client will connect to
30
+ nsec: Nostr private key in bech32 format
31
+ """
32
+ # Set log handling
33
+ if not NostrClient.logger.hasHandlers():
34
+ console_handler = logging.StreamHandler()
35
+ console_handler.setLevel(logging.INFO)
36
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
37
+ console_handler.setFormatter(formatter)
38
+ NostrClient.logger.addHandler(console_handler)
39
+
40
+ self.relay = relay
41
+ self.keys = Keys.parse(nsec)
42
+ self.nostr_signer = NostrSigner.keys(self.keys)
43
+ self.client = Client(self.nostr_signer)
44
+
45
+
46
+ async def connect(
47
+ self
48
+ ) -> str:
49
+
50
+ """Add relay to the NostrClient instance and connect to it.
51
+
52
+ Returns:
53
+ str: NostrClient.SUCCESS or NostrClient.ERROR
54
+ """
55
+ try:
56
+ await self.client.add_relay(self.relay)
57
+ NostrClient.logger.info(f"Relay {self.relay} succesfully added.")
58
+ await self.client.connect()
59
+ NostrClient.logger.info("Connected to relay.")
60
+ return NostrClient.SUCCESS
61
+ except Exception as e:
62
+ NostrClient.logger.error(f"Unable to connect to relay {self.relay}. Exception: {e}.")
63
+ return NostrClient.ERROR
64
+
65
+ async def publish_text_note(
66
+ self,
67
+ text: str
68
+ ) -> str:
69
+
70
+ """Publish kind 1 event (text note) to the relay
71
+
72
+ Args:
73
+ text: text to be published as kind 1 event
74
+
75
+ Returns:
76
+ str: event id if successful and "error" string if unsuccesful
77
+ """
78
+ builder = EventBuilder.text_note(text)
79
+
80
+ try:
81
+ output = await self.client.send_event_builder(builder)
82
+ NostrClient.logger.info(f"Text note published with event id: {output.id.to_bech32()}")
83
+ return output.id.to_bech32()
84
+ except Exception as e:
85
+ NostrClient.logger.error(f"Unable to publish text note to relay {self.relay}. Exception: {e}.")
86
+ return NostrClient.ERROR
87
+
88
+ async def publish_event(
89
+ self,
90
+ builder: EventBuilder
91
+ ) -> str:
92
+
93
+ """Publish generic Nostr event to the relay
94
+
95
+ Returns:
96
+ str: event id if successful or "error" string if unsuccesful
97
+ """
98
+ try:
99
+ output = await self.client.send_event_builder(builder)
100
+ NostrClient.logger.info(f"Event published with event id: {output.id.to_bech32()}")
101
+ return output.id.to_bech32()
102
+ except Exception as e:
103
+ NostrClient.logger.error(f"Unable to publish event to relay {self.relay}. Exception: {e}.")
104
+ return NostrClient.ERROR
105
+
106
+ async def publish_profile(self, name: str, about: str, picture: str) -> str:
107
+ """Publish a Nostr profile.
108
+
109
+ Args:
110
+ name: name of the Nostr profile
111
+ about: brief description about the profile
112
+ picture: url to a png file with a picture for the profile
113
+
114
+ Returns:
115
+ str: event id if successful or "error" string if unsuccesful
116
+ """
117
+ metadata_content = Metadata().set_name(name)
118
+ metadata_content = metadata_content.set_about(about)
119
+ metadata_content = metadata_content.set_picture(picture)
120
+
121
+ builder = EventBuilder.metadata(metadata_content)
122
+ try:
123
+ output = await self.client.send_event_builder(builder)
124
+ NostrClient.logger.info(f"Profile note published with event id: {output.id.to_bech32()}")
125
+ return output.id.to_bech32()
126
+ except Exception as e:
127
+ NostrClient.logger.error(f"Unable to publish profile to relay {self.relay}. Exception: {e}.")
128
+ return NostrClient.ERROR
129
+
130
+ @classmethod
131
+ def set_logging_level(cls, logging_level: int):
132
+ """
133
+ Set the logging level for the NostrClient logger.
134
+
135
+ Args:
136
+ logging_level (int): The logging level (e.g., logging.DEBUG, logging.INFO).
137
+ """
138
+ cls.logger.setLevel(logging_level)
139
+ for handler in cls.logger.handlers:
140
+ handler.setLevel(logging_level)
141
+ cls.logger.info(f"Logging level set to {logging.getLevelName(logging_level)}")
@@ -1,9 +1,7 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: agentstr
3
- Version: 0.0.9
3
+ Version: 0.0.10
4
4
  Summary: A library for collaborative AI agents
5
- Home-page: https://github.com/synvya/agentstr
6
- Author: Alejandro Gil
7
5
  Author-email: Alejandro Gil <info@synvya.com>
8
6
  License: MIT
9
7
  Project-URL: Homepage, https://github.com/synvya/agentstr
@@ -13,14 +11,13 @@ Keywords: AI,agents,collaboration,library
13
11
  Classifier: Programming Language :: Python :: 3
14
12
  Classifier: License :: OSI Approved :: MIT License
15
13
  Classifier: Operating System :: OS Independent
16
- Requires-Python: >=3.6
14
+ Requires-Python: <3.13,>=3.9
17
15
  Description-Content-Type: text/markdown
18
16
  License-File: LICENSE
19
17
  Requires-Dist: phidata>=2.7.0
20
18
  Requires-Dist: openai>=1.50.0
21
- Dynamic: author
22
- Dynamic: home-page
23
- Dynamic: requires-python
19
+ Requires-Dist: packaging>=24.0
20
+ Requires-Dist: nostr-sdk>=0.38.0
24
21
 
25
22
  AgentStr
26
23
  ========
@@ -28,6 +25,10 @@ AgentStr is an extension of [Phidata](https://www.phidata.com) AI agents that al
28
25
 
29
26
  The goal is for Agent A operated by Company A to be able to work with Agent B operated by Company B to achieve a common goal. For example: Company A wants to buy a product sold by Company B so Agent A and Agent B can coordinate and execute the transaction.
30
27
 
28
+ The basic communication tools are implemented in `agentstr/nostr.py`.
29
+
30
+ As a first example, AgentStr provides the tools to create and operate a marketplace using the [NIP-15](https://github.com/nostr-protocol/nips/blob/master/15.md) Nostr Marketplace as its foundation. The file `agentstr/marketplace.py` includes NIP-15 `merchant` and `customer` profiles implemented each as a Phidata Toolkit.
31
+
31
32
  # License
32
33
  This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
33
34
 
@@ -36,16 +37,13 @@ The library is in its infancy.
36
37
 
37
38
  Done:
38
39
  - Workflow to package and distribute the library
39
- - Create AgentStr as an extension of Phidata Agent
40
- - Test the addition of new properties and capabilities for the AgentStr class
40
+ - Users can create a Merchant profile and create an agent with the `merchant` toolkit that acts on behalf of the Merchant profile
41
+
41
42
 
42
43
  To be done:
43
- - Incorporate Nostr capabilities
44
- - Create unique public / private key identifiers for agent instances
45
- - Send and retreive messages via Nostr
46
- - Expose capabilities via Nostr
47
- - Agent B retrieves capabilities exposed by Agent A
48
- - Agent B coordinates transaction with Agent A
44
+ - Create a `marketplace` with `stalls`
45
+ - Merchants to define `products`
46
+ - Create a `customer` Toolkit
49
47
 
50
48
  # Installation
51
49
  AgentStr is offered as a python library available at https://pypi.org/project/agentstr/.
@@ -56,10 +54,11 @@ Here is an example on how to use the library:
56
54
  ```
57
55
  cd ~/
58
56
  python3 -m venv ~/.venvs/aienv
59
- source ~/.venvs/aienv/bin/acticate
57
+ source ~/.venvs/aienv/bin/activate
60
58
  ```
61
59
  2. Install the agentstr library
62
60
  ```
61
+ pip install --upgrade pip
63
62
  pip install agentstr
64
63
  mkdir ~/mysampleapp
65
64
  cd ~/mysampleapp
@@ -70,24 +69,43 @@ Here is an example on how to use the library:
70
69
  ```
71
70
  4. Copy paste this code to the main.py file
72
71
  ```
73
- from agentstr.core import AgentStr
74
- # Create the agent
75
- agent = AgentStr("Synvya Inc", "Seller")
72
+ from dotenv import load_dotenv
73
+ from os import getenv
74
+ from phi.agent import Agent
75
+ from phi.model.openai import OpenAIChat
76
+ from agentstr.marketplace import MerchantProfile, Merchant
76
77
 
77
- # Test AgentStr new capabilities
78
- print(f"Public key: {agent.get_public_key()}\nPrivate key: {agent.get_private_key()}")
79
- print(f"Company: {agent.get_company()}\nRole: {agent.get_role()}")
80
78
 
81
- # Test phidata inherited capabilities
82
- agent.print_response("Write two sentence poem for the love between the sun and the moon.")
79
+ profile = MerchantProfile(
80
+ "Synvya",
81
+ "Testing stuff",
82
+ "https://i.nostr.build/ocjZ5GlAKwrvgRhx.png",
83
+ getenv("NSEC_KEY")
84
+ )
85
+
86
+ agent = Agent(
87
+ name="Merchant Assistant",
88
+ model=OpenAIChat(id="gpt-4o"),
89
+ tools=[Merchant(merchant_profile=profile, relay="wss://relay.damus.io")],
90
+ show_tool_calls=True,
91
+ markdown=True,
92
+ debug_mode=True
93
+ )
94
+
95
+ agent.print_response("Publish the merchant information and tell me the event id used")
83
96
  ```
84
- 5. Run the code
97
+ 5. Export your OpenAI key and optionally a Nostr private key before running the code
85
98
  ```
99
+ export OPENAI_API_KEY="sk-***"
100
+ export NSEC_KEY="nsec***"
86
101
  python main.py
87
102
  ```
88
103
 
104
+ This example will attempt to load a Nostr private key defined as NSEC_KEY in bech32 format. If a private key is not provided, the `MerchantProfile` class initializer will assign it a new one.
105
+
89
106
  # Contributing
90
107
  Refer to [CONTRIBUTING.md](CONTRIBUTING.md) for specific instructions on installation instructions for developers and how to contribute.
91
108
 
92
109
  # Acknowledgments
93
- - [Phidata](https://www.phidata.com/.com/) - For building robust AI agents.
110
+ - [Phidata](https://www.phidata.com) - For building robust AI agents.
111
+ - [Rust-Nostr](https://rust-nostr.org/index.html) - For providing a python based Nostr SDK.
@@ -0,0 +1,8 @@
1
+ agentstr/__init__.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
2
+ agentstr/marketplace.py,sha256=XYF2ZrF6MpmxgCEOz3uN436cj7F4VHyUATFk8gmr7Zg,5245
3
+ agentstr/nostr.py,sha256=C5zMWKBbXLkarZxBoTydJCZVzqGoCogBd_9v8Gll0VU,5012
4
+ agentstr-0.0.10.dist-info/LICENSE,sha256=xF8akIKB07gOtkhjENT0xVbWGdFp4-srDKpZKjD03Js,1063
5
+ agentstr-0.0.10.dist-info/METADATA,sha256=GuVKTLwjr6Skj4HH387GQP7YYMnTKulqgh8OQuuJGvs,4082
6
+ agentstr-0.0.10.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
7
+ agentstr-0.0.10.dist-info/top_level.txt,sha256=KZObFRHppZvKUGYB_m9w5HhLwps7jj7w6Xrw73dH2ss,9
8
+ agentstr-0.0.10.dist-info/RECORD,,
agentstr/core.py DELETED
@@ -1,42 +0,0 @@
1
- from phi.agent import Agent
2
- from phi.model.openai import OpenAIChat
3
- from pydantic import Field
4
- from typing import Optional
5
-
6
-
7
- class AgentStr(Agent):
8
-
9
- # -*- Agent settings
10
- # Company operating the agent.
11
- # Used as seed for public / private key identifier together with the agent role
12
- company: str = None
13
-
14
- # -*- Agent public / private key identifiers
15
- # The public / private key should be deterministic for a given 'company' and 'role' combination
16
- # Public key for the agent
17
- npub: str = None
18
- # Private key for the agent
19
- nsec: str = None
20
-
21
- # Call the parent class (Agent) constructor
22
- def __init__(self, company: str, role: str):
23
- super().__init__(role = role, model=OpenAIChat(id="gpt-4o"))
24
- self.company = company
25
- self.npub = f"npub - {self.company} - {self.role}"
26
- self.nsec = f"nsec - {self.company} - {self.role}"
27
-
28
- def get_public_key(self) -> str:
29
- return self.npub
30
-
31
- def get_private_key(self) -> str:
32
- return self.nsec
33
-
34
- def get_company(self) -> str:
35
- return self.company
36
-
37
- def get_role(self) -> str:
38
- return self.role
39
-
40
- def add(a, b):
41
- """Add two numbers."""
42
- return a + b
@@ -1,7 +0,0 @@
1
- agentstr/__init__.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
2
- agentstr/core.py,sha256=xS5q8ltl81DqLjKgiVtjUQ5r-jbEPXXjTlJ0pWcV5p8,1214
3
- agentstr-0.0.9.dist-info/LICENSE,sha256=xF8akIKB07gOtkhjENT0xVbWGdFp4-srDKpZKjD03Js,1063
4
- agentstr-0.0.9.dist-info/METADATA,sha256=0H9Gophcg8p8Hw3vCZhQanTZTb9EGkCNvMWXsA_aOo0,3194
5
- agentstr-0.0.9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
6
- agentstr-0.0.9.dist-info/top_level.txt,sha256=KZObFRHppZvKUGYB_m9w5HhLwps7jj7w6Xrw73dH2ss,9
7
- agentstr-0.0.9.dist-info/RECORD,,