1+ {
2+ "nbformat" : 4 ,
3+ "nbformat_minor" : 0 ,
4+ "metadata" : {
5+ "colab" : {
6+ "provenance" : []
7+ },
8+ "kernelspec" : {
9+ "name" : " python3" ,
10+ "display_name" : " Python 3"
11+ },
12+ "language_info" : {
13+ "name" : " python"
14+ }
15+ },
16+ "cells" : [
17+ {
18+ "cell_type" : " code" ,
19+ "source" : [
20+ " !pip -q install \" uagents>=0.11.2\"\n " ,
21+ " \n " ,
22+ " import asyncio, random\n " ,
23+ " from typing import List, Dict, Optional\n " ,
24+ " from uagents import Agent, Context, Bureau, Model, Protocol\n " ,
25+ " \n " ,
26+ " class ServiceAnnounce(Model):\n " ,
27+ " category: str\n " ,
28+ " endpoint: str\n " ,
29+ " \n " ,
30+ " class ServiceQuery(Model):\n " ,
31+ " category: str\n " ,
32+ " \n " ,
33+ " class ServiceList(Model):\n " ,
34+ " addresses: List[str]\n " ,
35+ " \n " ,
36+ " class OfferRequest(Model):\n " ,
37+ " item: str\n " ,
38+ " max_price: int\n " ,
39+ " \n " ,
40+ " class Offer(Model):\n " ,
41+ " item: str\n " ,
42+ " price: int\n " ,
43+ " qty: int\n " ,
44+ " \n " ,
45+ " class Order(Model):\n " ,
46+ " item: str\n " ,
47+ " qty: int\n " ,
48+ " \n " ,
49+ " class Receipt(Model):\n " ,
50+ " item: str\n " ,
51+ " qty: int\n " ,
52+ " total: int\n " ,
53+ " ok: bool\n " ,
54+ " note: Optional[str] = None"
55+ ],
56+ "metadata" : {
57+ "id" : " JLZCduHv7Mc0"
58+ },
59+ "execution_count" : null ,
60+ "outputs" : []
61+ },
62+ {
63+ "cell_type" : " code" ,
64+ "source" : [
65+ " registry_proto = Protocol(name=\" registry\" , version=\" 1.0\" )\n " ,
66+ " trade_proto = Protocol(name=\" trade\" , version=\" 1.0\" )\n " ,
67+ " \n " ,
68+ " directory = Agent(name=\" directory\" , seed=\" dir-seed-001\" )\n " ,
69+ " seller = Agent(name=\" seller\" , seed=\" seller-seed-001\" )\n " ,
70+ " buyer = Agent(name=\" buyer\" , seed=\" buyer-seed-001\" )\n " ,
71+ " \n " ,
72+ " directory.include(registry_proto)\n " ,
73+ " seller.include(trade_proto)\n " ,
74+ " buyer.include(registry_proto)\n " ,
75+ " buyer.include(trade_proto)\n " ,
76+ " \n " ,
77+ " @registry_proto.on_message(model=ServiceAnnounce)\n " ,
78+ " async def on_announce(ctx: Context, sender: str, msg: ServiceAnnounce):\n " ,
79+ " reg = await ctx.storage.get(\" reg\" ) or {}\n " ,
80+ " reg.setdefault(msg.category, set()).add(sender)\n " ,
81+ " await ctx.storage.set(\" reg\" , reg)\n " ,
82+ " ctx.logger.info(f\" Registered {sender} under '{msg.category}'\" )\n " ,
83+ " \n " ,
84+ " @registry_proto.on_message(model=ServiceQuery)\n " ,
85+ " async def on_query(ctx: Context, sender: str, msg: ServiceQuery):\n " ,
86+ " reg = await ctx.storage.get(\" reg\" ) or {}\n " ,
87+ " addrs = sorted(list(reg.get(msg.category, set())))\n " ,
88+ " await ctx.send(sender, ServiceList(addresses=addrs))\n " ,
89+ " ctx.logger.info(f\" Returned {len(addrs)} providers for '{msg.category}'\" )"
90+ ],
91+ "metadata" : {
92+ "id" : " S_Z5bjy27MZ9"
93+ },
94+ "execution_count" : null ,
95+ "outputs" : []
96+ },
97+ {
98+ "cell_type" : " code" ,
99+ "source" : [
100+ " CATALOG: Dict[str, Dict[str, int]] = {\n " ,
101+ " \" camera\" : {\" price\" : 120, \" qty\" : 3},\n " ,
102+ " \" laptop\" : {\" price\" : 650, \" qty\" : 2},\n " ,
103+ " \" headphones\" : {\" price\" : 60, \" qty\" : 5},\n " ,
104+ " }\n " ,
105+ " \n " ,
106+ " @seller.on_event(\" startup\" )\n " ,
107+ " async def seller_start(ctx: Context):\n " ,
108+ " await ctx.send(directory.address, ServiceAnnounce(category=\" electronics\" , endpoint=seller.address))\n " ,
109+ " ctx.logger.info(\" Seller announced to directory\" )\n " ,
110+ " \n " ,
111+ " @trade_proto.on_message(model=OfferRequest)\n " ,
112+ " async def on_offer_request(ctx: Context, sender: str, req: OfferRequest):\n " ,
113+ " item = CATALOG.get(req.item)\n " ,
114+ " if not item:\n " ,
115+ " await ctx.send(sender, Offer(item=req.item, price=0, qty=0))\n " ,
116+ " return\n " ,
117+ " price = max(1, int(item[\" price\" ] * (0.9 + 0.2 * random.random())))\n " ,
118+ " if price > req.max_price or item[\" qty\" ] <= 0:\n " ,
119+ " await ctx.send(sender, Offer(item=req.item, price=0, qty=0))\n " ,
120+ " return\n " ,
121+ " await ctx.send(sender, Offer(item=req.item, price=price, qty=item[\" qty\" ]))\n " ,
122+ " ctx.logger.info(f\" Offered {req.item} at {price} with qty {item['qty']}\" )\n " ,
123+ " \n " ,
124+ " @trade_proto.on_message(model=Order)\n " ,
125+ " async def on_order(ctx: Context, sender: str, order: Order):\n " ,
126+ " item = CATALOG.get(order.item)\n " ,
127+ " if not item or item[\" qty\" ] < order.qty:\n " ,
128+ " await ctx.send(sender, Receipt(item=order.item, qty=0, total=0, ok=False, note=\" Not enough stock\" ))\n " ,
129+ " return\n " ,
130+ " total = item[\" price\" ] * order.qty\n " ,
131+ " item[\" qty\" ] -= order.qty\n " ,
132+ " await ctx.send(sender, Receipt(item=order.item, qty=order.qty, total=total, ok=True, note=\" Thanks!\" ))"
133+ ],
134+ "metadata" : {
135+ "id" : " PGQUQi347MXJ"
136+ },
137+ "execution_count" : null ,
138+ "outputs" : []
139+ },
140+ {
141+ "cell_type" : " code" ,
142+ "source" : [
143+ " @buyer.on_event(\" startup\" )\n " ,
144+ " async def buyer_start(ctx: Context):\n " ,
145+ " ctx.logger.info(\" Buyer querying directory for electronics...\" )\n " ,
146+ " resp = await ctx.ask(directory.address, ServiceQuery(category=\" electronics\" ), expects=ServiceList, timeout=5.0)\n " ,
147+ " sellers = resp.addresses if resp else []\n " ,
148+ " if not sellers:\n " ,
149+ " return\n " ,
150+ " target = sellers[0]\n " ,
151+ " desired = \" laptop\"\n " ,
152+ " budget = 700\n " ,
153+ " ctx.logger.info(f\" Requesting offer for '{desired}' within budget {budget} from {target}\" )\n " ,
154+ " offer = await ctx.ask(target, OfferRequest(item=desired, max_price=budget), expects=Offer, timeout=5.0)\n " ,
155+ " if not offer or offer.price <= 0:\n " ,
156+ " return\n " ,
157+ " qty = 1 if offer.qty >= 1 else 0\n " ,
158+ " if qty == 0:\n " ,
159+ " return\n " ,
160+ " ctx.logger.info(f\" Placing order for {qty} x {offer.item} at {offer.price}\" )\n " ,
161+ " receipt = await ctx.ask(target, Order(item=offer.item, qty=qty), expects=Receipt, timeout=5.0)\n " ,
162+ " if receipt and receipt.ok:\n " ,
163+ " ctx.logger.info(f\" ORDER SUCCESS: {receipt.qty} x {receipt.item} | total={receipt.total}\" )"
164+ ],
165+ "metadata" : {
166+ "id" : " cwxrJFiW7MUF"
167+ },
168+ "execution_count" : null ,
169+ "outputs" : []
170+ },
171+ {
172+ "cell_type" : " code" ,
173+ "execution_count" : 2 ,
174+ "metadata" : {
175+ "colab" : {
176+ "base_uri" : " https://localhost:8080/"
177+ },
178+ "id" : " qXW2QFCw6VK9" ,
179+ "outputId" : " 0dc7b642-1248-455f-c006-dcba3af77945"
180+ },
181+ "outputs" : [
182+ {
183+ "output_type" : " stream" ,
184+ "name" : " stdout" ,
185+ "text" : [
186+ " INFO: [directory]: Starting agent with address: agent1qvgxjeed0mvkhfmvnywkpyyeadkkj07v9gvcfz40v8p5738c55x2vn86mez\n " ,
187+ " INFO: [directory]: Starting agent with address: agent1qvgxjeed0mvkhfmvnywkpyyeadkkj07v9gvcfz40v8p5738c55x2vn86mez\n " ,
188+ " WARNING: [directory]: No endpoints provided. Skipping registration: Agent won't be reachable.\n " ,
189+ " WARNING: [directory]: No endpoints provided. Skipping registration: Agent won't be reachable.\n " ,
190+ " INFO: [seller]: Starting agent with address: agent1q0wv76nvkr60wvfvmf0jjf2qygl2csarhf09dglcd3xh2an2yxv9uvar5la\n " ,
191+ " INFO: [seller]: Starting agent with address: agent1q0wv76nvkr60wvfvmf0jjf2qygl2csarhf09dglcd3xh2an2yxv9uvar5la\n " ,
192+ " WARNING: [seller]: No endpoints provided. Skipping registration: Agent won't be reachable.\n " ,
193+ " WARNING: [seller]: No endpoints provided. Skipping registration: Agent won't be reachable.\n " ,
194+ " INFO: [buyer]: Starting agent with address: agent1qwk5eey3t36pswtv4f7w5euf4myycnqar8lsq3dd2hq4gcmk6y2vxenlqwq\n " ,
195+ " INFO: [buyer]: Starting agent with address: agent1qwk5eey3t36pswtv4f7w5euf4myycnqar8lsq3dd2hq4gcmk6y2vxenlqwq\n " ,
196+ " WARNING: [buyer]: No endpoints provided. Skipping registration: Agent won't be reachable.\n " ,
197+ " WARNING: [buyer]: No endpoints provided. Skipping registration: Agent won't be reachable.\n " ,
198+ " INFO: [seller]: Seller announced to directory\n " ,
199+ " INFO: [seller]: Seller announced to directory\n " ,
200+ " INFO: [buyer]: Buyer querying directory for electronics...\n " ,
201+ " INFO: [buyer]: Buyer querying directory for electronics...\n " ,
202+ " ERROR: [buyer]: Exception in startup handler: 'InternalContext' object has no attribute 'ask'\n " ,
203+ " Traceback (most recent call last):\n " ,
204+ " File \" /usr/local/lib/python3.12/dist-packages/uagents/agent.py\" , line 1169, in run_startup_tasks\n " ,
205+ " await handler(ctx)\n " ,
206+ " File \" /tmp/ipython-input-3257487494.py\" , line 111, in buyer_start\n " ,
207+ " resp = await ctx.ask(directory.address, ServiceQuery(category=\" electronics\" ), expects=ServiceList, timeout=5.0)\n " ,
208+ " ^^^^^^^\n " ,
209+ " AttributeError: 'InternalContext' object has no attribute 'ask'\n " ,
210+ " ERROR: [buyer]: Exception in startup handler: 'InternalContext' object has no attribute 'ask'\n " ,
211+ " Traceback (most recent call last):\n " ,
212+ " File \" /usr/local/lib/python3.12/dist-packages/uagents/agent.py\" , line 1169, in run_startup_tasks\n " ,
213+ " await handler(ctx)\n " ,
214+ " File \" /tmp/ipython-input-3257487494.py\" , line 111, in buyer_start\n " ,
215+ " resp = await ctx.ask(directory.address, ServiceQuery(category=\" electronics\" ), expects=ServiceList, timeout=5.0)\n " ,
216+ " ^^^^^^^\n " ,
217+ " AttributeError: 'InternalContext' object has no attribute 'ask'\n " ,
218+ " INFO: [bureau]: Starting server on http://0.0.0.0:8000 (Press CTRL+C to quit)\n " ,
219+ " INFO: [bureau]: Starting server on http://0.0.0.0:8000 (Press CTRL+C to quit)\n " ,
220+ " WARNING: [directory]: Received message with unrecognized schema digest: model:b87a4c644ee706f817dfffc3097f23cbdc491c7ea359d4e7fac6d53a6f4aef59\n " ,
221+ " WARNING: [directory]: Received message with unrecognized schema digest: model:b87a4c644ee706f817dfffc3097f23cbdc491c7ea359d4e7fac6d53a6f4aef59\n " ,
222+ " ERROR: [buyer]: Exception in interval handler: object NoneType can't be used in 'await' expression\n " ,
223+ " Traceback (most recent call last):\n " ,
224+ " File \" /usr/local/lib/python3.12/dist-packages/uagents/agent.py\" , line 109, in _run_interval\n " ,
225+ " await func(ctx)\n " ,
226+ " File \" /tmp/ipython-input-3257487494.py\" , line 138, in periodic_discovery\n " ,
227+ " seen = await ctx.storage.get(\" seen\" ) or 0\n " ,
228+ " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n " ,
229+ " TypeError: object NoneType can't be used in 'await' expression\n " ,
230+ " ERROR: [buyer]: Exception in interval handler: object NoneType can't be used in 'await' expression\n " ,
231+ " Traceback (most recent call last):\n " ,
232+ " File \" /usr/local/lib/python3.12/dist-packages/uagents/agent.py\" , line 109, in _run_interval\n " ,
233+ " await func(ctx)\n " ,
234+ " File \" /tmp/ipython-input-3257487494.py\" , line 138, in periodic_discovery\n " ,
235+ " seen = await ctx.storage.get(\" seen\" ) or 0\n " ,
236+ " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n " ,
237+ " TypeError: object NoneType can't be used in 'await' expression\n " ,
238+ " ERROR: [buyer]: Exception in interval handler: object NoneType can't be used in 'await' expression\n " ,
239+ " Traceback (most recent call last):\n " ,
240+ " File \" /usr/local/lib/python3.12/dist-packages/uagents/agent.py\" , line 109, in _run_interval\n " ,
241+ " await func(ctx)\n " ,
242+ " File \" /tmp/ipython-input-3257487494.py\" , line 138, in periodic_discovery\n " ,
243+ " seen = await ctx.storage.get(\" seen\" ) or 0\n " ,
244+ " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n " ,
245+ " TypeError: object NoneType can't be used in 'await' expression\n " ,
246+ " ERROR: [buyer]: Exception in interval handler: object NoneType can't be used in 'await' expression\n " ,
247+ " Traceback (most recent call last):\n " ,
248+ " File \" /usr/local/lib/python3.12/dist-packages/uagents/agent.py\" , line 109, in _run_interval\n " ,
249+ " await func(ctx)\n " ,
250+ " File \" /tmp/ipython-input-3257487494.py\" , line 138, in periodic_discovery\n " ,
251+ " seen = await ctx.storage.get(\" seen\" ) or 0\n " ,
252+ " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n " ,
253+ " TypeError: object NoneType can't be used in 'await' expression\n " ,
254+ " INFO: [bureau]: Shutting down server...\n " ,
255+ " INFO: [bureau]: Shutting down server...\n "
256+ ]
257+ },
258+ {
259+ "output_type" : " stream" ,
260+ "name" : " stderr" ,
261+ "text" : [
262+ " ERROR:asyncio:Exception in callback Task.task_wakeup(<Task finishe...> result=None>)\n " ,
263+ " handle: <Handle Task.task_wakeup(<Task finishe...> result=None>)>\n " ,
264+ " Traceback (most recent call last):\n " ,
265+ " File \" /usr/lib/python3.12/asyncio/events.py\" , line 88, in _run\n " ,
266+ " self._context.run(self._callback, *self._args)\n " ,
267+ " File \" /usr/lib/python3.12/asyncio/tasks.py\" , line 721, in cancel\n " ,
268+ " if child.cancel(msg=msg):\n " ,
269+ " ^^^^^^^^^^^^^^^^^^^^^\n " ,
270+ " File \" /usr/lib/python3.12/asyncio/tasks.py\" , line 721, in cancel\n " ,
271+ " if child.cancel(msg=msg):\n " ,
272+ " ^^^^^^^^^^^^^^^^^^^^^\n " ,
273+ " File \" /usr/lib/python3.12/asyncio/tasks.py\" , line 721, in cancel\n " ,
274+ " if child.cancel(msg=msg):\n " ,
275+ " ^^^^^^^^^^^^^^^^^^^^^\n " ,
276+ " [Previous line repeated 988 more times]\n " ,
277+ " RecursionError: maximum recursion depth exceeded\n "
278+ ]
279+ },
280+ {
281+ "output_type" : " stream" ,
282+ "name" : " stdout" ,
283+ "text" : [
284+ " \n " ,
285+ " ✅ Demo run complete.\n " ,
286+ " \n "
287+ ]
288+ }
289+ ],
290+ "source" : [
291+ " @buyer.on_interval(period=6.0)\n " ,
292+ " async def periodic_discovery(ctx: Context):\n " ,
293+ " seen = await ctx.storage.get(\" seen\" ) or 0\n " ,
294+ " if seen >= 1:\n " ,
295+ " return\n " ,
296+ " await ctx.storage.set(\" seen\" , seen + 1)\n " ,
297+ " ctx.logger.info(\" Periodic discovery tick -> re-query directory\" )\n " ,
298+ " resp = await ctx.ask(directory.address, ServiceQuery(category=\" electronics\" ), expects=ServiceList, timeout=3.0)\n " ,
299+ " n = len(resp.addresses) if resp else 0\n " ,
300+ " ctx.logger.info(f\" Periodic: directory reports {n} seller(s)\" )\n " ,
301+ " \n " ,
302+ " bureau = Bureau()\n " ,
303+ " bureau.add(directory)\n " ,
304+ " bureau.add(seller)\n " ,
305+ " bureau.add(buyer)\n " ,
306+ " \n " ,
307+ " async def run_demo(seconds=10):\n " ,
308+ " task = asyncio.create_task(bureau.run_async())\n " ,
309+ " try:\n " ,
310+ " await asyncio.sleep(seconds)\n " ,
311+ " finally:\n " ,
312+ " task.cancel()\n " ,
313+ " try:\n " ,
314+ " await task\n " ,
315+ " except asyncio.CancelledError:\n " ,
316+ " pass\n " ,
317+ " print(\"\\ n✅ Demo run complete.\\ n\" )\n " ,
318+ " \n " ,
319+ " try:\n " ,
320+ " loop = asyncio.get_running_loop()\n " ,
321+ " await run_demo(10)\n " ,
322+ " except RuntimeError:\n " ,
323+ " asyncio.run(run_demo(10))"
324+ ]
325+ }
326+ ]
327+ }
0 commit comments