Quick links
- Repo: ai-agent-ag2-examples/product-hunt
- README: README.md
- Agent core: agent.py
- FastAPI server: server.py
- Sample UI: web/index.html
What you’ll build
- A Python ProductHuntAgent that decides when to fetch top posts, run timeframe queries, search Algolia, or fire the confetti action.
- A
ProductHuntServicethat talks to Product Hunt’s GraphQL API (with optional token) and its public Algolia index. - A FastAPI backend exposing
/tools/*JSON endpoints plus a streaming/agentSSE route tuned for CometChat. - A Product Hunt–inspired static UI that mounts the CometChat widget and interprets the
CONFETTIaction payload.
Prerequisites
- Python 3.10 or newer and
pip. - Environment variables:
OPENAI_API_KEY(required).- Optional:
PRODUCTHUNT_API_TOKEN(enables live top-post queries),PRODUCT_HUNT_AGENT_MODEL(defaultgpt-4o-mini),PRODUCT_HUNT_AGENT_TEMPERATURE(default0.3),PRODUCT_HUNT_AGENT_MAX_ITEMS(default5).
- Install dependencies with
pip install -r requirements.txt. - A CometChat app where you’ll register the agent and embed the widget.
How it works
ProductHuntServiceclamps user input, builds GraphQL queries for top posts, translates natural-language timeframes to UTC windows, and falls back gracefully when credentials are missing.- The
ProductHuntAgentinspects each message, emits structuredtool_call_*SSE events around tool invocations, and streams OpenAI-generated answers grounded in tool output. - Confetti requests stay server-driven: the agent returns an
action: "CONFETTI"payload that frontends can render safely. server.pyhosts FastAPI routes for REST access and a/agentendpoint that batches SSE events so CometChat can replay them in real time.- The static
web/index.htmlmocks a Product Hunt landing page, mounts CometChat, and demonstrates how to consume leaderboard data plus the confetti action.
Step 1 — Talk to Product Hunt
File:agent.py
ProductHuntServicesigns GraphQL calls whenPRODUCTHUNT_API_TOKENis present; otherwise it returns empty lists and logs helpful warnings.get_top_products_by_timeframenormalizes phrases liketoday,last week,2024-09-01, orfrom:2024-08-01 to:2024-08-15, generating the ISO timestamps Product Hunt expects.search_productswraps the public Algolia index using the bundled app ID/key, trimming the response to the requested limit.to_markdown_tableconverts product lists into Markdown tables so chat replies stay scannable (and safe for CometChat rendering).
Step 2 — Build the AG2 agent
Also inagent.py:
ProductHuntAgentloads configuration from environment variables, enforces sane limits, and initializes the OpenAI client._decide_actionuses lightweight heuristics to route messages totop,top_timeframe,search,confetti, or fallbackadvice.- Each tool call yields
tool_call_startandtool_call_resultSSE frames with deterministic IDs, mirroring CometChat’s expectations. _generate_answercalls OpenAI with a Product Hunt–specific system prompt, grounding responses with the structured payload when available._stream_textbreaks completions into word chunks so the frontend sees incremental updates instead of one large blob.
Step 3 — Serve tools and SSE with FastAPI
File:server.py
- FastAPI spins up with permissive CORS and a lifespan hook that instantiates
ProductHuntAgentonce. /tools/top,/tools/top_timeframe,/tools/search, and/tools/confettivalidate input via Pydantic models and return the raw tool payloads (including Markdown tables or action structures)./agentextracts the latest user message, streams the agent’s SSE output viaStreamingResponse, and keeps buffers disabled for true real-time delivery./healthand/provide lightweight diagnostics you can hit from monitoring or your deployment platform.
Step 4 — Run the agent locally
http://localhost:8000/ to confirm the service and keep an eye on logs for Product Hunt token warnings.
Step 5 — Stream a conversation
tool_call_* events with Product Hunt data followed by streaming text_message chunks and a [DONE] sentinel—exactly what CometChat’s AI agents API consumes.
Step 6 — Plug into CometChat
- In your CometChat dashboard: AI Agents → Add Agent → AG2.
- Set the Agent ID (e.g.,
producthunt-ag2) and point the deployment URL to your/agentendpoint (must be HTTPS in production). - Map the
CONFETTIaction to your widget handler; the sample UI demonstrates how to read the payload and executecanvas-confetti. - Optionally preload greetings, suggested prompts (“What are today’s top 3 launches?”), and any additional frontend actions you want to support.
Step 7 — Test the REST tools
GET /health→{"status":"healthy","agent_initialized":true}POST /tools/topwith{"limit":3}→ current leaders by total votes (empty if no Product Hunt token).POST /tools/top_timeframewith{"timeframe":"yesterday","tz":"America/Los_Angeles"}→ timeframe metadata plus ranked posts.POST /tools/searchwith{"query":"notion","limit":5}→ Algolia search results.POST /tools/confettiwith overrides like{"colors":["#ff577f","#7b5cff"],"particleCount":400}→ structured frontend action payload.
Security & production checklist
- Keep
OPENAI_API_KEYandPRODUCTHUNT_API_TOKENserver-side only; never expose them in the web embed. - Add CORS restrictions and authentication (API keys, JWT, or CometChat-signed requests) before deploying.
- Rate-limit
/agentand/tools/*, and cache Product Hunt responses to avoid repeatedly hitting the GraphQL API. - Log tool usage and errors without storing sensitive conversation content; monitor for expired Product Hunt tokens.
- Run behind HTTPS with streaming-friendly proxies (e.g., Nginx with buffering disabled on
/agent).
Troubleshooting
- Empty top-product responses: set
PRODUCTHUNT_API_TOKEN; without it, GraphQL queries return no edges. - Algolia search is blank: confirm outbound network access and that you stay within Algolia’s public rate limits.
- SSE stops instantly: make sure your hosting provider allows streaming and preserves
Cache-Control: no-cacheheaders. - Confetti never fires in the UI: ensure your frontend listens for
action === "CONFETTI"and passes the payload tocanvas-confetti. - Replies feel generic: check server logs to verify tool calls succeeded; the agent falls back to advice mode when payloads are empty.