diff --git a/CHANGELOG.md b/CHANGELOG.md index 36fdfe2..034016b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this repository are documented in this file. ## [Unreleased] ### Added +- `pharmacy-locator-agent/`: Beginner-friendly uAgent combining ASI:One location extraction with the free OpenStreetMap Overpass API - `Browser-based-agents/playwright/job-application-agent/`: Playwright + ASI:One + Stripe job application agent. Orchestrates a Chromium session to auto-fill Greenhouse application forms using a stored user profile, with LLM-drafted free-text answers via ASI:One, Stripe-gated premium features, and resume ingestion. - GSSoC '26 label system: [.github/labels/gssoc-labels.json](.github/labels/gssoc-labels.json) definitions, [.github/scripts/create-gssoc-labels.sh](.github/scripts/create-gssoc-labels.sh) bootstrap script, `gssoc-label-bootstrap` workflow (auto-create/update labels), `gssoc-label-sync` workflow (copy `gssoc26`/`level1-3` labels from linked issues to PRs for dashboard tracking), and [docs/GSSOC.md](docs/GSSOC.md) guide - `langchain-agents/deep-agents/hackflow-agent/`: LangChain Deep Agents hackathon intelligence agent for ASI:One. Three-subagent research pipeline (event_finder, sponsor_researcher, winner_researcher), Stripe-gated full analysis, ASI:One primary LLM with fallbacks, Tavily web search, and persisted cross-turn follow-up memory. @@ -16,23 +17,20 @@ All notable changes to this repository are documented in this file. - CI gates: `contributor-path-check`, `changelog-check`, and `review-required` (no merge without approval when branch protection is enabled) - Issue templates: contributor good-first tasks and real-time agent challenge - `.github/BRANCH_PROTECTION.md` maintainer setup for required reviews and status checks - -### Changed -- `community_agent/` moved to `contributors/community_agent/` — all new community agents must use `contributors//` -- `CONTRIBUTING.md`, `README.md`, `ISSUES_GUIDE.md`, and PR template updated for contributor folder workflow -- `security-scanner-agent/`: LLM-powered code security analysis agent that scans code snippets via ASI:One and returns structured vulnerability reports (type, severity, line number, description, suggested fix). Built on a multi-agent Bureau using the standard Agent Chat Protocol; ASI:One-compatible and discoverable on Agentverse. - `ticketlens-agent/`: Live real-time travel discovery AI agent powered by TicketLens MCP. High-precision reasoning utilizing the ASI1 LLM, persistent `uAgents` storage, and directly actionable booking deep links. - `openclaw/`: OpenClaw examples — `fetchai-openclaw-orchestrator` (connector + orchestrator, repo health analyzer) and `agentverse-caller` (OpenClaw skill to search and message Agentverse agents) - `stripe-payment-agents/youtube-growth-analyzer-agent`: Multi-agent YouTube channel analyzer with free preview and Stripe-gated premium report flow, built for Agentverse/ASI:One chat + payment protocols - `openai-agent-sdk/Appliance Auto Whisperer`: Multi-agent right-to-repair system — Gemini Vision (via OpenAI SDK) identifies broken parts from photos, Bright Data scrapes prices from 6+ retailers, YouTube Data API finds repair tutorials. Orchestrator coordinates workers via REST fan-out with Docker Compose support. +### Changed - `openai-agent-sdk/Appliance Auto Whisperer`: Multi-agent right-to-repair assistant for ASI:One with orchestrator + parts/tutorial workers, streamlined bureau-first architecture, and updated README demo screenshots - - `google-adk/google-trends-agent`: Fetch.ai uAgent that answers natural-language Google Trends questions with per-query Stripe payment gating, using ASI:One LLM for BigQuery SQL generation and result interpretation - - `stripe-payment-agents/conversational-property-finder`: ASI1 conversational property search agent (Repliers MLS, optional Stripe details paywall, OpenAI/regex parsing) - `ag2-agents/` — Two AG2 (formerly AutoGen) multi-agent examples: a payment approval workflow and a research synthesis team, both integrated with uAgents via the A2A protocol - `community_agent/` — AI Community Growth Agent for planning events, conferences, and hackathons +- `community_agent/` moved to `contributors/community_agent/` — all new community agents must use `contributors//` +- `CONTRIBUTING.md`, `README.md`, `ISSUES_GUIDE.md`, and PR template updated for contributor folder workflow +- `security-scanner-agent/`: LLM-powered code security analysis agent that scans code snippets via ASI:One and returns structured vulnerability reports (type, severity, line number, description, suggested fix). Built on a multi-agent Bureau using the standard Agent Chat Protocol; ASI:One-compatible and discoverable on Agentverse. - `CONTRIBUTING.md` with agent-focused contribution workflow and merge policy - Pull request CI workflow with checks for `stargazer-gate`, `lint`, `format`, `typecheck`, `validate-architecture`, and `test` - `.github/CODEOWNERS` for required reviewer routing @@ -48,9 +46,5 @@ All notable changes to this repository are documented in this file. - Missing `requirements.txt` for `community_agent`, `av-script-example`, `asi1-llm-example`, `advance-agent-examples/{search,policy,basic}_agent` - Missing `.env.example` for `community_agent`, `duffel-agent`, `deploy-agent-on-av`, `asi-cloud-agent`, `pdf-summariser-example`, `flight-tracker-openai-workflow-agent`, `google-genai-parallel-processing/brand-management-agent`, `Rag-agent/ango`, `asi1-llm-example` - Missing `README.md` for `duffel-agent`, `deploy-agent-on-av` - - -### Changed - - `README.md` rewritten with project overview, quickstart guide, categorized examples index table, folder structure, Docker instructions, and resource links - `CONTRIBUTING.md` expanded with setup script reference, tagging/categorization guidance, Docker support section, and issue flow references \ No newline at end of file diff --git a/pharmacy-locator-agent/.env.example b/pharmacy-locator-agent/.env.example new file mode 100644 index 0000000..3baf3f9 --- /dev/null +++ b/pharmacy-locator-agent/.env.example @@ -0,0 +1,3 @@ +# API Key for the ASI:One LLM +# Get your key from: https://asi1.ai/ +ASI_ONE_API_KEY=your_asi1_api_key_here diff --git a/pharmacy-locator-agent/README.md b/pharmacy-locator-agent/README.md new file mode 100644 index 0000000..98409ef --- /dev/null +++ b/pharmacy-locator-agent/README.md @@ -0,0 +1,64 @@ +# Pharmacy Locator Agent + +- **Category:** `Getting Started`, `Integration` +- **Difficulty:** Beginner + +A beginner-friendly uAgent that helps users find nearby pharmacies. It uses the [ASI:One LLM](https://asi1.ai/) to parse natural language location queries and connects to the free [OpenStreetMap Overpass API](https://overpass-api.de/) to fetch real-world data. + +## Prerequisites + +- Python 3.10+ +- An API key for [ASI:One](https://asi1.ai/) (to parse user intent/locations). + +> **Note:** The OpenStreetMap Overpass API is completely free and public, requiring no API key. + +## Installation + +Navigate to the agent directory and install dependencies: + +```bash +cd pharmacy-locator-agent +python -m venv .venv +source .venv/bin/activate # On Windows: .venv\Scripts\activate +pip install -r requirements.txt +``` + +Set up your environment variables: + +```bash +cp .env.example .env +``` + +Edit the `.env` file and add your `ASI_ONE_API_KEY`. + +## Running the Agent + +Start the agent: + +```bash +python agent.py +``` + +The agent will print its address in the console. + +## Expected Output + +You can interact with the agent via the Agentverse chat interface or a local chat script. + +**User:** `Find me a pharmacy near London` + +**Agent:** +``` +Searching for pharmacies in **London**... 🔍 + +Here are 5 pharmacies I found in London: + +1. **Boots** + 📍 Address: Oxford Street London + 📞 Phone: +44 20 1234 5678 + 🕒 Hours: Mo-Su 08:00-22:00 + +... + +*Data provided by OpenStreetMap (Overpass API)* +``` diff --git a/pharmacy-locator-agent/agent.py b/pharmacy-locator-agent/agent.py new file mode 100644 index 0000000..f9c427d --- /dev/null +++ b/pharmacy-locator-agent/agent.py @@ -0,0 +1,228 @@ +""" +Pharmacy Locator Agent + +A uAgent that helps users find nearby pharmacies using the ASI:One LLM +to parse location queries and the free OpenStreetMap Overpass API to fetch data. +""" + +from __future__ import annotations + +import os +import asyncio +from datetime import datetime, timezone +from uuid import uuid4 + +# Python 3.13 compatibility: Create an event loop if one doesn't exist +try: + asyncio.get_running_loop() +except RuntimeError: + asyncio.set_event_loop(asyncio.new_event_loop()) + +import httpx +from dotenv import load_dotenv +from uagents import Agent, Context, Protocol + + +load_dotenv() + +from uagents_core.contrib.protocols.chat import ( + ChatAcknowledgement, + ChatMessage, + TextContent, + chat_protocol_spec, +) + +ASI_ONE_API_KEY = (os.getenv("ASI_ONE_API_KEY") or "").strip() + + +def _call_asi1_extract_location(user_text: str) -> str: + """Use ASI:One to extract just the city/neighborhood name from the user query.""" + if not ASI_ONE_API_KEY: + raise RuntimeError( + "ASI_ONE_API_KEY is not set. Please add it to your .env file." + ) + + system_prompt = ( + "You are a helpful geographic assistant. " + "Extract ONLY the city or neighborhood name from the user's request. " + "Do not include any extra words, punctuation, or explanations. " + "If no location is found, output exactly: UNKNOWN" + ) + + payload = { + "model": "asi1", + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_text}, + ], + "temperature": 0.1, + "max_tokens": 50, + } + + headers = { + "Authorization": f"Bearer {ASI_ONE_API_KEY}", + "Content-Type": "application/json", + } + + resp = httpx.post( + "https://api.asi1.ai/v1/chat/completions", + json=payload, + headers=headers, + timeout=30.0, + ) + resp.raise_for_status() + + data = resp.json() + choices = data.get("choices", []) + if not choices: + raise RuntimeError("No response from ASI:One") + + location = choices[0]["message"]["content"].strip() + return location + + +def _fetch_pharmacies_from_overpass(location: str) -> list[dict]: + """Fetch pharmacies using the OpenStreetMap Overpass API.""" + overpass_url = "http://overpass-api.de/api/interpreter" + + # Overpass QL to find pharmacies in a specific named area + query = f""" + [out:json]; + area[name="{location}"]->.searchArea; + node["amenity"="pharmacy"](area.searchArea); + out 5; + """ + + headers = {"User-Agent": "PharmacyLocatorAgent/1.0 (Fetch.ai)", "Accept": "*/*"} + resp = httpx.post(overpass_url, data={"data": query}, headers=headers, timeout=30.0) + resp.raise_for_status() + + data = resp.json() + elements = data.get("elements", []) + + results = [] + for el in elements: + tags = el.get("tags", {}) + name = tags.get("name", "Unnamed Pharmacy") + address = tags.get("addr:street", "") + " " + tags.get("addr:city", "") + phone = tags.get("phone", "No phone listed") + opening_hours = tags.get("opening_hours", "Hours not specified") + + results.append( + { + "name": name, + "address": address.strip() or "Address not specified", + "phone": phone, + "opening_hours": opening_hours, + } + ) + + return results + + +chat_proto = Protocol(spec=chat_protocol_spec) + + +@chat_proto.on_message(ChatMessage) +async def on_chat(ctx: Context, sender: str, msg: ChatMessage): + # Acknowledge the message immediately + await ctx.send( + sender, + ChatAcknowledgement( + timestamp=datetime.now(timezone.utc), + acknowledged_msg_id=msg.msg_id, + ), + ) + + # Extract user text + parts = [ + item.text for item in msg.content if isinstance(item, TextContent) and item.text + ] + user_text = "\n".join(parts).strip() + + if not user_text: + welcome = ( + "Hi! I'm the Pharmacy Locator Agent 🏥\n\n" + "Tell me where you are, and I'll find nearby pharmacies for you.\n" + "Example: 'Find a pharmacy in London' or 'Need meds near Brooklyn'" + ) + await _send_reply(ctx, sender, welcome) + return + + try: + # Step 1: Use LLM to extract the location from natural language + location = _call_asi1_extract_location(user_text) + ctx.logger.info(f"Extracted location: {location}") + + if location == "UNKNOWN": + await _send_reply( + ctx, + sender, + "I couldn't figure out the location from your message. Please specify a city or neighborhood!", + ) + return + + await _send_reply( + ctx, sender, f"Searching for pharmacies in **{location}**... 🔍" + ) + + # Step 2: Query the Overpass API + pharmacies = _fetch_pharmacies_from_overpass(location) + + if not pharmacies: + await _send_reply( + ctx, + sender, + f"Sorry, I couldn't find any pharmacies listed in OpenStreetMap for {location}.", + ) + return + + # Step 3: Format the response + response_text = ( + f"Here are {len(pharmacies)} pharmacies I found in {location}:\n\n" + ) + for i, p in enumerate(pharmacies, 1): + response_text += f"{i}. **{p['name']}**\n" + response_text += f" 📍 Address: {p['address']}\n" + response_text += f" 📞 Phone: {p['phone']}\n" + response_text += f" 🕒 Hours: {p['opening_hours']}\n\n" + + response_text += "\n*Data provided by OpenStreetMap (Overpass API)*" + + await _send_reply(ctx, sender, response_text.strip()) + + except Exception as e: + ctx.logger.exception("Agent encountered an error") + await _send_reply(ctx, sender, f"Sorry, I encountered an error: {str(e)[:200]}") + + +async def _send_reply(ctx: Context, sender: str, text: str): + await ctx.send( + sender, + ChatMessage( + timestamp=datetime.now(timezone.utc), + msg_id=uuid4(), + content=[TextContent(type="text", text=text)], + ), + ) + + +@chat_proto.on_message(ChatAcknowledgement) +async def on_ack(ctx: Context, sender: str, msg: ChatAcknowledgement): + ctx.logger.info(f"ACK received from {sender}") + + +agent = Agent( + name="pharmacy-locator-agent", seed="random_seed_for_pharmacy_locator_agent_123" +) +agent.include(chat_proto, publish_manifest=True) + + +@agent.on_event("startup") +async def on_startup(ctx: Context): + ctx.logger.info(f"Pharmacy Locator Agent started -> {agent.address}") + ctx.logger.info(f"ASI:One API key present: {bool(ASI_ONE_API_KEY)}") + + +if __name__ == "__main__": + agent.run() diff --git a/pharmacy-locator-agent/requirements.txt b/pharmacy-locator-agent/requirements.txt new file mode 100644 index 0000000..00c0781 --- /dev/null +++ b/pharmacy-locator-agent/requirements.txt @@ -0,0 +1,4 @@ +httpx>=0.27.0 +uagents>=0.24.0 +python-dotenv>=1.0.0 + diff --git a/pharmacy-locator-agent/test_agent.py b/pharmacy-locator-agent/test_agent.py new file mode 100644 index 0000000..a430743 --- /dev/null +++ b/pharmacy-locator-agent/test_agent.py @@ -0,0 +1,55 @@ +import asyncio + +try: + asyncio.get_running_loop() +except RuntimeError: + asyncio.set_event_loop(asyncio.new_event_loop()) + +from uagents import Agent, Context, Bureau +from uagents_core.contrib.protocols.chat import ChatMessage, TextContent +from uuid import uuid4 +from datetime import datetime, timezone + +# Import the pharmacy agent we just built +from agent import agent as pharmacy_agent + +# Create a mock user agent +user = Agent(name="user", seed="mock_user_seed_123") + + +@user.on_event("startup") +async def on_startup(ctx: Context): + query = "Find me a pharmacy near London" + ctx.logger.info(f"User asking: '{query}'") + + # Send message to pharmacy agent + await ctx.send( + pharmacy_agent.address, + ChatMessage( + timestamp=datetime.now(timezone.utc), + msg_id=uuid4(), + content=[TextContent(type="text", text=query)], + ), + ) + + +@user.on_message(ChatMessage) +async def handle_reply(ctx: Context, sender: str, msg: ChatMessage): + # Print the reply from the pharmacy agent + for item in msg.content: + if isinstance(item, TextContent): + print("\n" + "=" * 50) + print("🏥 REPLY FROM PHARMACY AGENT:") + print("=" * 50) + print(item.text) + print("=" * 50 + "\n") + + +# Run both agents together in a Bureau +bureau = Bureau() +bureau.add(pharmacy_agent) +bureau.add(user) + +if __name__ == "__main__": + print("Starting local test...") + bureau.run()