{"id":"cd64d0ee-0b12-4288-90d5-0fb87b5d3460","entityType":"agent","slug":"crewai-marouahattab-ai-agents-product-search","name":"ai-agents-product-search","canonicalUrl":"https://www.xpersona.co/agent/crewai-marouahattab-ai-agents-product-search","canonicalPath":"/agent/crewai-marouahattab-ai-agents-product-search","generatedAt":"2026-10-10T02:54:21.559Z","source":"GITHUB_OPENCLEW","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-05-12T06:46:15.815Z","emptyReason":null},"description":"A hands-on learning project exploring multi-agent AI systems with CrewAI. Build a system that searches, scrapes, and compares product prices using coordinated AI agents. 🤖 ai-agents-product-search: Complete Guide to AI Agents with CrewAI This is a **learning project** to understand how AI Agents work using the **CrewAI** framework. This README explains every concept, every line of code, and every tool in detail. --- 📚 Table of Contents 1. $1 2. $1 3. $1 4. $1 5. $1 6. $1 7. $1 8. $1 9. $1 10. $1 --- 🤖 What is an AI Agent? The Simple Explanation An **AI Agent** is more than just a","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. Last updated 5/12/2026.","installCommand":"git clone https://github.com/MarouaHattab/ai-agents-product-search.git","sourceUrl":"https://github.com/MarouaHattab/ai-agents-product-search","homepage":null,"primaryLinks":[{"label":"View Source","url":"https://github.com/MarouaHattab/ai-agents-product-search","kind":"source"}],"safetyScore":66,"overallRank":18.4,"popularityScore":0,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"A hands-on learning project exploring multi-agent AI systems with CrewAI. Build a system that searches, scrapes, and compares product prices using coordinated A"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-05-12T06:46:15.815Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[{"label":"crewai","status":"self-declared"},{"label":"multi-agent","status":"self-declared"}],"verifiedCount":0,"selfDeclaredCount":3,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"},{"key":"crewai","type":"capability","support":"supported","confidenceSource":"profile","notes":"Declared in agent profile metadata"},{"key":"multi-agent","type":"capability","support":"supported","confidenceSource":"profile","notes":"Declared in agent profile metadata"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile capability:crewai|supported|profile capability:multi-agent|supported|profile"}},"adoption":{"evidence":{"source":"no-adoption-signals","verified":false,"confidence":"low","updatedAt":"2026-05-12T06:46:15.815Z","emptyReason":"No source adoption metrics were available."},"stars":0,"forks":0,"downloads":null,"packageName":null,"latestVersion":null,"tractionLabel":null},"release":{"evidence":{"source":"agent-index","verified":false,"confidence":"medium","updatedAt":"2026-05-12T06:46:15.814Z","emptyReason":null},"lastUpdatedAt":"2026-05-12T06:46:15.815Z","lastCrawledAt":"2026-05-12T06:46:15.814Z","lastIndexedAt":null,"nextCrawlAt":"2026-05-19T06:46:15.814Z","lastVerifiedAt":null,"highlights":[]},"execution":{"evidence":{"source":"GITHUB OPENCLEW","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"git clone https://github.com/MarouaHattab/ai-agents-product-search.git","setupComplexity":"low","setupSteps":["Setup complexity is LOW. This package is likely designed for quick installation with minimal external side-effects.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/crewai-marouahattab-ai-agents-product-search/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/crewai-marouahattab-ai-agents-product-search/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/crewai-marouahattab-ai-agents-product-search/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/crewai-marouahattab-ai-agents-product-search/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/crewai-marouahattab-ai-agents-product-search/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/crewai-marouahattab-ai-agents-product-search/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"GITHUB_OPENCLEW","generatedAt":"2026-10-10T02:54:21.559Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/crewai-marouahattab-ai-agents-product-search/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/crewai-marouahattab-ai-agents-product-search/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/crewai-marouahattab-ai-agents-product-search/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/crewai-marouahattab-ai-agents-product-search/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"GITHUB OPENCLEW","verified":false,"confidence":"high","updatedAt":"2026-05-12T06:46:15.815Z","emptyReason":null},"readme":"# 🤖 ai-agents-product-search: Complete Guide to AI Agents with CrewAI\n\nThis is a **learning project** to understand how AI Agents work using the **CrewAI** framework. This README explains every concept, every line of code, and every tool in detail.\n\n---\n\n## 📚 Table of Contents\n\n1. [What is an AI Agent?](#-what-is-an-ai-agent)\n2. [What is CrewAI?](#-what-is-crewai)\n3. [Project Architecture](#-project-architecture)\n4. [Tools Explained in Detail](#-tools-explained-in-detail)\n5. [Agents Explained in Detail](#-agents-explained-in-detail)\n6. [Tasks Explained in Detail](#-tasks-explained-in-detail)\n7. [Pydantic Models (Data Validation)](#-pydantic-models-data-validation)\n8. [The Crew Assembly](#-the-crew-assembly)\n9. [The API Layer](#-the-api-layer)\n10. [How to Run](#-how-to-run)\n\n---\n\n## 🤖 What is an AI Agent?\n\n### The Simple Explanation\nAn **AI Agent** is more than just a chatbot. Think of it like this:\n\n| Chatbot | AI Agent |\n|---------|----------|\n| Answers questions | Takes actions |\n| Single response | Multi-step reasoning |\n| No memory between messages | Remembers context |\n| No tools | Uses tools (search, scrape, calculate) |\n\n### The Technical Definition\nAn AI Agent is a system where:\n1. An **LLM** (Large Language Model like GPT, Llama, Qwen) acts as the \"brain\"\n2. The LLM is given a **role** and **goal**\n3. The LLM can use **tools** to interact with the real world\n4. The LLM follows a **reasoning loop**: Think → Act → Observe → Repeat\n\n### The ReAct Pattern\nCrewAI agents follow the **ReAct** (Reasoning + Acting) pattern:\n```\nThought: I need to search for MSI laptops on Tunisian websites\nAction: Search Engine Tool\nAction Input: {\"query\": \"MSI Thin 15 Tunisianet\"}\nObservation: [Results from Tavily...]\nThought: I found 3 relevant links. Now I need to...\n```\n\n---\n\n## 🚀 What is CrewAI?\n\n**CrewAI** is a Python framework for building multi-agent systems. It allows you to:\n\n1. **Define Agents** with specific roles (like \"Researcher\", \"Writer\", \"Analyst\")\n2. **Give Agents Tools** (search engines, web scrapers, calculators)\n3. **Assign Tasks** to agents\n4. **Orchestrate** how agents work together (sequential or parallel)\n\n### Core Concepts\n\n| Concept | Description |\n|---------|-------------|\n| **Agent** | An AI \"worker\" with a role, goal, and backstory |\n| **Tool** | A function the agent can call (search, scrape, etc.) |\n| **Task** | A specific job assigned to an agent |\n| **Crew** | A team of agents working together |\n| **Process** | How tasks are executed (sequential/hierarchical) |\n\n---\n\n## 🏗️ Project Architecture\n\n```\nAI Agents using CrewAI/\n│\n├── api.py                 # FastAPI server (entry point)\n├── main.py                # CLI version (alternative entry point)\n├── requirements.txt       # Python dependencies\n├── .env                   # API keys (TAVILY, SCRAPEGRAPH)\n│\n├── src/\n│   ├── config.py          # Configuration (LLM model, websites list)\n│   ├── tools.py           # Tool definitions (Search, Scrape)\n│   ├── agents.py          # Agent definitions (4 agents)\n│   ├── tasks.py           # Task definitions (4 tasks)\n│   ├── models.py          # Pydantic models (data validation)\n│   └── crew.py            # Crew assembly\n│\n└── ai-agent-output/       # Generated files\n    ├── step_1_suggested_search_queries.json\n    ├── step_2_search_results.json\n    ├── step_3_extracted_products.json\n    └── step_4_procurement_report.html\n```\n\n---\n\n## 🔧 Tools Explained in Detail\n\nTools are defined in `src/tools.py`. They allow agents to interact with external services.\n\n### What is a Tool?\n\nA tool is just a **Python function** that:\n1. Has a descriptive name\n2. Has a clear docstring (the agent reads this!)\n3. Takes specific inputs\n4. Returns useful output\n\n### Tool 1: Search Engine Tool (Tavily)\n\n```python\nfrom crewai.tools import tool\n\n# Global client reference\n_search_client = None\n\n@tool(\"Search Engine Tool\")\ndef search_engine_tool(query: str) -> dict:\n    \"\"\"\n    Search for products using the Tavily search engine.\n    Useful for finding product pages on specific Tunisian websites.\n    \n    Args:\n        query: The search query string to find products on Tunisian websites.\n    \n    Returns:\n        dict: Search results from Tavily\n    \"\"\"\n    global _search_client\n    if _search_client is None:\n        return {\"error\": \"Search client not initialized\"}\n    return _search_client.search(query)\n```\n\n#### Code Breakdown:\n\n| Line | Explanation |\n|------|-------------|\n| `from crewai.tools import tool` | Import the `@tool` decorator from CrewAI |\n| `_search_client = None` | Global variable to hold the Tavily client |\n| `@tool(\"Search Engine Tool\")` | **Decorator** that registers this function as a CrewAI tool. The string is the tool's name that agents will see. |\n| `def search_engine_tool(query: str) -> dict:` | The function takes a `query` string and returns a dictionary |\n| `\"\"\"...\"\"\"` | **CRITICAL**: The docstring. Agents read this to understand WHEN and HOW to use the tool! |\n| `global _search_client` | Access the global client variable |\n| `return _search_client.search(query)` | Call Tavily's search API |\n\n#### Why Tavily?\n- **AI-optimized**: Returns clean content, not HTML soup\n- **Relevance scores**: Each result has a confidence score (0.0 to 1.0)\n- **Fast**: Designed for agent workflows\n\n---\n\n### Tool 2: Web Scraping Tool (ScrapeGraphAI)\n\n```python\n@tool(\"Web Scraping Tool\")\ndef web_scraping_tool(page_url: str) -> dict:\n    \"\"\"\n    Scrape product details from a web page using AI.\n    \n    Args:\n        page_url: The URL of the product page to scrape.\n    \n    Returns:\n        dict: Extracted product details from the page\n    \"\"\"\n    global _scrape_client\n    if _scrape_client is None:\n        return {\"error\": \"Scrape client not initialized\"}\n    \n    details = _scrape_client.smartscraper(\n        website_url=page_url,\n        user_prompt=f\"Extract ```json\\n{SingleExtractedProduct.model_json_schema()}\\n``` From the web page\"\n    )\n    return {\n        \"page_url\": page_url,\n        \"details\": details\n    }\n```\n\n#### Code Breakdown:\n\n| Line | Explanation |\n|------|-------------|\n| `page_url: str` | Input is a URL string |\n| `_scrape_client.smartscraper(...)` | Call ScrapeGraphAI's API |\n| `website_url=page_url` | The page to scrape |\n| `user_prompt=...` | Tell the AI what data to extract |\n| `SingleExtractedProduct.model_json_schema()` | Uses our Pydantic model to tell the scraper exactly what JSON structure we want |\n\n#### Why ScrapeGraphAI?\n- **AI-powered**: Uses LLMs to understand web pages\n- **No selectors needed**: You don't need to write CSS selectors or XPath\n- **Adaptive**: Works even if the website changes its design\n\n---\n\n### Tool Initialization Function\n\n```python\ndef create_tools(search_client, scrape_client):\n    \"\"\"Create CrewAI tools for the agents.\"\"\"\n    global _search_client, _scrape_client\n    \n    # Set global client references\n    _search_client = search_client\n    _scrape_client = scrape_client\n    \n    return search_engine_tool, web_scraping_tool\n```\n\nThis function:\n1. Takes the initialized API clients as parameters\n2. Stores them in global variables so the tools can access them\n3. Returns the tool functions to be assigned to agents\n\n---\n\n## 🤖 Agents Explained in Detail\n\nAgents are defined in `src/agents.py`. Each agent has:\n\n| Attribute | Purpose |\n|-----------|---------|\n| `role` | The job title (what they do) |\n| `goal` | What they're trying to achieve |\n| `backstory` | Their personality/expertise (shapes LLM behavior) |\n| `llm` | Which language model to use |\n| `tools` | List of tools they can use |\n| `verbose` | Whether to print their thinking process |\n\n---\n\n### Agent 1: Search Queries Recommendation Agent\n\n```python\nsearch_queries_agent = Agent(\n    role=\"Search Queries Recommendation Agent\",\n    goal=\"\\n\".join([\n        \"To provide a list of suggested search queries to be passed to the search engine.\",\n        \"The queries must be varied and looking for specific items.\"\n    ]),\n    backstory=(\n        \"The agent is designed to help in looking for products by providing \"\n        \"a list of suggested search queries to be passed to the search engine \"\n        \"based on the context provided.\"\n    ),\n    llm=llm,\n    verbose=True\n)\n```\n\n#### What This Agent Does:\n- **No tools**: This agent only thinks, it doesn't act\n- **Input**: Product name like \"MSI Thin 15\"\n- **Output**: List of search queries like:\n  - \"MSI Thin 15 prix Tunisie\"\n  - \"MSI Thin 15 Mytek\"\n  - \"MSI Thin 15 Tunisianet stock\"\n\n#### Why No Tools?\nThis agent is a \"planner\". It uses the LLM's knowledge to generate varied search queries. It doesn't need to search itself.\n\n---\n\n### Agent 2: Search Engine Agent\n\n```python\nsearch_engine_agent = Agent(\n    role=\"Search Engine Agent\",\n    goal=\"To execute search queries using the Search Engine Tool and collect product search results.\",\n    backstory=(\n        \"You are a search specialist. For EACH search query you receive, you MUST use the \"\n        \"'Search Engine Tool' with the query as input. Do NOT make up search results - \"\n        \"always use the tool to get real results. Collect all valid results from the tool's response.\"\n    ),\n    llm=llm,\n    verbose=True,\n    tools=[search_engine_tool]  # 👈 HAS A TOOL\n)\n```\n\n#### What This Agent Does:\n- **Has the Search Tool**: Can actually search the web\n- **Input**: List of queries from Agent 1\n- **Output**: List of URLs with titles and relevance scores\n\n#### Key Backstory Elements:\n- `\"For EACH search query you receive, you MUST use the 'Search Engine Tool'\"` - Forces the agent to use the tool\n- `\"Do NOT make up search results\"` - Prevents hallucination\n\n---\n\n### Agent 3: Web Scraping Agent\n\n```python\nscraping_agent = Agent(\n    role=\"Web Scraping Agent\",\n    goal=\"To extract product details from websites using the Web Scraping Tool.\",\n    backstory=(\n        \"You are a web scraping specialist. For EACH product URL you receive, you MUST use the \"\n        \"'Web Scraping Tool' with the page_url as input. Do NOT make up product details - \"\n        \"always use the tool to get real data from the actual web pages.\"\n    ),\n    llm=llm,\n    verbose=True,\n    tools=[web_scraping_tool]  # 👈 HAS A TOOL\n)\n```\n\n#### What This Agent Does:\n- **Has the Scrape Tool**: Can extract data from web pages\n- **Input**: List of URLs from Agent 2\n- **Output**: Structured product data (price, specs, availability)\n\n---\n\n### Agent 4: Procurement Report Author Agent\n\n```python\nreport_author_agent = Agent(\n    role=\"Procurement Report Author Agent\",\n    goal=\"To generate a professional HTML page for the procurement report.\",\n    backstory=(\n        \"The agent is designed to assist in generating a professional HTML page \"\n        \"for the procurement report after looking into a list of products.\"\n    ),\n    llm=llm,\n    verbose=True\n)\n```\n\n#### What This Agent Does:\n- **No tools**: Pure intelligence/writing\n- **Input**: Structured product data from Agent 3\n- **Output**: Beautiful HTML report with comparison table\n\n---\n\n## 📋 Tasks Explained in Detail\n\nTasks are defined in `src/tasks.py`. Each task tells an agent exactly what to do.\n\n### Task Structure\n\n```python\nTask(\n    description=\"...\",      # What to do (instructions)\n    expected_output=\"...\",  # What format the output should be\n    output_json=Model,      # Pydantic model for validation\n    output_file=\"...\",      # Where to save the output\n    agent=agent            # Which agent handles this\n)\n```\n\n---\n\n### Task 1: Generate Search Queries\n\n```python\nsearch_queries_task = Task(\n    description=\"\\n\".join([\n        \"The user is looking to buy {product_name} in Tunisia.\",\n        \"Focus ONLY on PC accessories, computers (laptops/desktops), and smartphones.\",\n        \"Target these specific Tunisian tech websites: {websites_list}\",\n        \"Generate search queries to find this product specifically on these stores.\",\n        \"Generate at maximum {no_keywords} queries.\",\n        \"The search keywords must be in {language} language.\",\n        \"Search keywords must contain specific model names if applicable.\"\n    ]),\n    expected_output=\"A JSON object containing a list of suggested search queries.\",\n    output_json=SuggestedSearchQueries,\n    output_file=os.path.join(OUTPUT_DIR, \"step_1_suggested_search_queries.json\"),\n    agent=search_queries_agent\n)\n```\n\n#### Key Points:\n- `{product_name}`, `{websites_list}`, etc. are **template variables** filled at runtime\n- `output_json=SuggestedSearchQueries` tells CrewAI to validate the output against our Pydantic model\n- `output_file` saves the result automatically\n\n---\n\n### Task 2: Execute Search Queries\n\n```python\nsearch_engine_task = Task(\n    description=\"\\n\".join([\n        \"Search for the products using the generated queries on the specified Tunisian websites.\",\n        \"Use the Search Engine Tool to execute each search query.\",\n        \"You have to collect results from multiple search queries.\",\n        \"Ignore any suspicious links or non-ecommerce single product website links.\",\n        \"Ignore any search results with confidence score less than {score_th}.\",\n        \"Ensure the results are relevant to {product_name}.\",\n        \"\",\n        \"IMPORTANT: Your output MUST be a JSON object with a 'results' key containing an ARRAY of search results.\",\n        \"Each item in the array must have: title, url, content, score, search_query\"\n    ]),\n    expected_output='''A JSON object with this EXACT structure:\n{\n  \"results\": [\n    {\"title\": \"...\", \"url\": \"...\", \"content\": \"...\", \"score\": 0.8, \"search_query\": \"...\"},\n    {\"title\": \"...\", \"url\": \"...\", \"content\": \"...\", \"score\": 0.7, \"search_query\": \"...\"}\n  ]\n}\nThe 'results' field MUST be an ARRAY (list), not a dictionary.''',\n    output_json=AllSearchResults,\n    output_file=os.path.join(OUTPUT_DIR, \"step_2_search_results.json\"),\n    agent=search_engine_agent\n)\n```\n\n#### Key Points:\n- **Explicit instructions** about using the tool\n- **Score threshold** (`{score_th}`) filters low-quality results\n- **Explicit JSON example** in `expected_output` helps the LLM format correctly\n\n---\n\n### Task 3: Scrape Product Details\n\n```python\nscraping_task = Task(\n    description=\"\\n\".join([\n        \"Extract product details from the found ecommerce store page URLs.\",\n        \"Use the Web Scraping Tool to extract details from each product URL.\",\n        \"Collect the best {top_recommendations_no} products.\",\n        \"CRITICAL: Sort the extracted products by 'product_current_price' in ASCENDING order.\",\n        \"Ensure the currency is TND (Tunisian Dinar) if available.\",\n        \"Verify that the product is actually available/in stock if possible.\",\n        \"\",\n        \"IMPORTANT: Your output MUST be a JSON object with a 'products' key (NOT 'results') containing an ARRAY.\"\n    ]),\n    expected_output='''A JSON object with this EXACT structure:\n{\n  \"products\": [\n    {\n      \"page_url\": \"https://...\",\n      \"product_title\": \"Product Name\",\n      \"product_current_price\": 1999.0,\n      ...\n    }\n  ]\n}\nThe key MUST be 'products' (not 'results').''',\n    output_json=AllExtractedProducts,\n    output_file=os.path.join(OUTPUT_DIR, \"step_3_extracted_products.json\"),\n    agent=scraping_agent\n)\n```\n\n#### Key Points:\n- **Sorting instruction**: Products sorted by price (cheapest first)\n- **Key name emphasis**: `'products'` not `'results'` (common LLM mistake)\n\n---\n\n### Task 4: Generate Report\n\n```python\nreport_task = Task(\n    description=\"\\n\".join([\n        \"Generate a professional HTML report for the found products.\",\n        \"Use Tailwind CSS for styling (via CDN) to make it look modern and premium.\",\n        f\"Context: {COMPANY_CONTEXT}\",\n        \"The report MUST list the products in order of Price: Low to High.\",\n        \"Include a summary table comparing the prices across the different Tunisian stores.\",\n        \"Highlight the 'Best Deal' (lowest price) and 'Best Value' (balance of specs/price).\",\n        \"Structure:\",\n        \"1. Executive Summary & Best Deal\",\n        \"2. Price Comparison Table\",\n        \"3. Detailed Product Findings (Sorted Cheapest to Expensive)\",\n        \"4. Recommendations\"\n    ]),\n    expected_output=\"A professional HTML page for the procurement report.\",\n    output_file=os.path.join(OUTPUT_DIR, \"step_4_procurement_report.html\"),\n    agent=report_author_agent\n)\n```\n\n#### Key Points:\n- **No `output_json`**: The output is HTML, not JSON\n- **Clear structure**: Tells the agent exactly how to organize the report\n- **Styling**: Uses Tailwind CSS for modern look\n\n---\n\n## 📊 Pydantic Models (Data Validation)\n\nModels are defined in `src/models.py`. They ensure agents return correctly formatted data.\n\n### Why Use Pydantic Models?\n\nWithout validation, an LLM might return:\n```json\n{\"items\": [{\"name\": \"laptop\", \"cost\": \"1999 TND\"}]}\n```\n\nWith Pydantic, we enforce:\n```json\n{\"products\": [{\"product_title\": \"...\", \"product_current_price\": 1999.0}]}\n```\n\n---\n\n### Model 1: SuggestedSearchQueries\n\n```python\nclass SuggestedSearchQueries(BaseModel):\n    \"\"\"Schema for search query suggestions.\"\"\"\n    queries: List[str] = Field(\n        ...,\n        title=\"Suggested search queries to be passed to the search engine\",\n        min_length=1,\n        max_length=MAX_KEYWORDS\n    )\n```\n\n| Field | Type | Constraints |\n|-------|------|-------------|\n| `queries` | `List[str]` | Min 1, Max 10 items |\n\n---\n\n### Model 2: SingleSearchResult & AllSearchResults\n\n```python\nclass SingleSearchResult(BaseModel):\n    \"\"\"Schema for a single search result.\"\"\"\n    title: str\n    url: str = Field(..., title=\"The page URL\")\n    content: str\n    score: float\n    search_query: str\n\nclass AllSearchResults(BaseModel):\n    \"\"\"Schema for all search results.\"\"\"\n    results: List[SingleSearchResult]\n```\n\n---\n\n### Model 3: SingleExtractedProduct & AllExtractedProducts\n\n```python\nclass SingleExtractedProduct(BaseModel):\n    \"\"\"Schema for a single extracted product.\"\"\"\n    page_url: str = Field(..., title=\"The original URL of the product page\")\n    product_title: str = Field(..., title=\"The title of the product\")\n    product_image_url: str = Field(..., title=\"The URL of the product image\")\n    product_url: str = Field(..., title=\"The URL of the product\")\n    product_current_price: float = Field(..., title=\"The current price of the product\")\n    product_original_price: Optional[float] = Field(\n        default=None,\n        title=\"The original price of the product before discount. Set to None if no discount\"\n    )\n    product_discount_percentage: Optional[float] = Field(\n        default=None,\n        title=\"The discount percentage of the product. Set to None if no discount\"\n    )\n    product_specs: List[ProductSpec] = Field(\n        ...,\n        title=\"The specifications of the product.\",\n        min_length=1,\n        max_length=5\n    )\n    agent_recommendation_rank: int = Field(\n        ...,\n        title=\"The rank of the product (out of 5, Higher is Better)\"\n    )\n    agent_recommendation_notes: List[str] = Field(\n        ...,\n        title=\"Notes on why you would recommend or not recommend this product\"\n    )\n\nclass AllExtractedProducts(BaseModel):\n    \"\"\"Schema for all extracted products.\"\"\"\n    products: List[SingleExtractedProduct]\n```\n\n#### Key Points:\n- `Optional[float]` means the field can be `None`\n- `Field(..., title=\"...\")` provides documentation for the LLM\n- Constraints like `min_length=1` ensure valid data\n\n---\n\n## 🎭 The Crew Assembly\n\nThe crew is assembled in `src/crew.py`:\n\n```python\nfrom crewai import Crew, Process\n\ndef create_crew(agents, tasks):\n    \"\"\"Create the procurement crew.\"\"\"\n    search_queries_agent, search_engine_agent, scraping_agent, report_author_agent = agents\n    search_queries_task, search_engine_task, scraping_task, report_task = tasks\n    \n    crew = Crew(\n        agents=[\n            search_queries_agent,\n            search_engine_agent,\n            scraping_agent,\n            report_author_agent\n        ],\n        tasks=[\n            search_queries_task,\n            search_engine_task,\n            scraping_task,\n            report_task\n        ],\n        process=Process.sequential  # 👈 KEY: Tasks run one after another\n    )\n    \n    return crew\n```\n\n### Process Types\n\n| Process | Description |\n|---------|-------------|\n| `Process.sequential` | Tasks run in order. Task 2 waits for Task 1 to finish. Output of Task 1 is passed to Task 2. |\n| `Process.hierarchical` | A \"manager\" agent assigns tasks dynamically. |\n\n---\n\n## 🌐 The API Layer\n\nThe API is defined in `api.py`:\n\n```python\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel\n\napp = FastAPI(title=\"Tunisian Tech Hunter Agent API\")\n\nclass ProductRequest(BaseModel):\n    product_name: str\n\n@app.post(\"/api/search\")\nasync def search_product(request: ProductRequest):\n    \"\"\"Trigger the agent to search for a product.\"\"\"\n    result = run_crew(request.product_name)  # Starts CrewAI\n    return {\"status\": \"success\", \"data\": {\"raw_output\": str(result)}}\n```\n\n### How It Works:\n1. User sends POST to `/api/search` with `{\"product_name\": \"MSI Thin 15\"}`\n2. FastAPI validates the input using `ProductRequest`\n3. `run_crew()` function initializes all clients, agents, and tasks\n4. `crew.kickoff(inputs={...})` starts the agent chain\n5. Result is returned as JSON\n\n---\n\n## 🚀 How to Run\n\n### 1. Install Dependencies\n```bash\npython -m venv venv\n.\\venv\\Scripts\\activate\npip install -r requirements.txt\n```\n\n### 2. Set Up Environment\nCreate `.env` file:\n```env\nTAVILY_API_KEY=your_key_here\nSCRAPEGRAPH_API_KEY=your_key_here\n```\n\n### 3. Start Ollama\n```bash\nollama serve\nollama pull qwen2.5-coder:7b\n```\n\n### 4. Run the API\n```bash\npython api.py\n```\n\n### 5. Test with Postman\n- **Method**: POST\n- **URL**: `http://localhost:8000/api/search`\n- **Body**:\n```json\n{\n  \"product_name\": \"MSI Thin 15\"\n}\n```\n\n---\n\n## 🎓 Key Learnings\n\n1. **Agents are LLMs with purpose**: The role, goal, and backstory shape behavior\n2. **Tools are the agent's hands**: Without tools, agents can only think\n3. **Tasks are instructions**: Clear descriptions = better results\n4. **Pydantic enforces structure**: Prevents LLM output chaos\n5. **Model size matters**: 7B+ models are needed for reliable tool calling\n6. **Sequential process**: Output flows from one agent to the next\n\n---\n\n*Happy Learning! 🚀*\n","readmeExcerpt":"🤖 ai-agents-product-search: Complete Guide to AI Agents with CrewAI This is a **learning project** to understand how AI Agents work using the **CrewAI** framework. This README explains every concept, every line of code, and every tool in detail. --- 📚 Table of Contents 1. $1 2. $1 3. $1 4. $1 5. $1 6. $1 7. $1 8. $1 9. $1 10. $1 --- 🤖 What is an AI Agent? The Simple Explanation An **AI Agent** is more than just a ","codeSnippets":[],"executableExamples":[{"language":"text","snippet":"Thought: I need to search for MSI laptops on Tunisian websites\nAction: Search Engine Tool\nAction Input: {\"query\": \"MSI Thin 15 Tunisianet\"}\nObservation: [Results from Tavily...]\nThought: I found 3 relevant links. Now I need to..."},{"language":"text","snippet":"AI Agents using CrewAI/\n│\n├── api.py                 # FastAPI server (entry point)\n├── main.py                # CLI version (alternative entry point)\n├── requirements.txt       # Python dependencies\n├── .env                   # API keys (TAVILY, SCRAPEGRAPH)\n│\n├── src/\n│   ├── config.py          # Configuration (LLM model, websites list)\n│   ├── tools.py           # Tool definitions (Search, Scrape)\n│   ├── agents.py          # Agent definitions (4 agents)\n│   ├── tasks.py           # Task definitions (4 tasks)\n│   ├── models.py          # Pydantic models (data validation)\n│   └── crew.py            # Crew assembly\n│\n└── ai-agent-output/       # Generated files\n    ├── step_1_suggested_search_queries.json\n    ├── step_2_search_results.json\n    ├── step_3_extracted_products.json\n    └── step_4_procurement_report.html"},{"language":"python","snippet":"from crewai.tools import tool\n\n# Global client reference\n_search_client = None\n\n@tool(\"Search Engine Tool\")\ndef search_engine_tool(query: str) -> dict:\n    \"\"\"\n    Search for products using the Tavily search engine.\n    Useful for finding product pages on specific Tunisian websites.\n    \n    Args:\n        query: The search query string to find products on Tunisian websites.\n    \n    Returns:\n        dict: Search results from Tavily\n    \"\"\"\n    global _search_client\n    if _search_client is None:\n        return {\"error\": \"Search client not initialized\"}\n    return _search_client.search(query)"},{"language":"python","snippet":"@tool(\"Web Scraping Tool\")\ndef web_scraping_tool(page_url: str) -> dict:\n    \"\"\"\n    Scrape product details from a web page using AI.\n    \n    Args:\n        page_url: The URL of the product page to scrape.\n    \n    Returns:\n        dict: Extracted product details from the page\n    \"\"\"\n    global _scrape_client\n    if _scrape_client is None:\n        return {\"error\": \"Scrape client not initialized\"}\n    \n    details = _scrape_client.smartscraper(\n        website_url=page_url,\n        user_prompt=f\"Extract"},{"language":"text","snippet":"#### Code Breakdown:\n\n| Line | Explanation |\n|------|-------------|\n| `page_url: str` | Input is a URL string |\n| `_scrape_client.smartscraper(...)` | Call ScrapeGraphAI's API |\n| `website_url=page_url` | The page to scrape |\n| `user_prompt=...` | Tell the AI what data to extract |\n| `SingleExtractedProduct.model_json_schema()` | Uses our Pydantic model to tell the scraper exactly what JSON structure we want |\n\n#### Why ScrapeGraphAI?\n- **AI-powered**: Uses LLMs to understand web pages\n- **No selectors needed**: You don't need to write CSS selectors or XPath\n- **Adaptive**: Works even if the website changes its design\n\n---\n\n### Tool Initialization Function"},{"language":"text","snippet":"This function:\n1. Takes the initialized API clients as parameters\n2. Stores them in global variables so the tools can access them\n3. Returns the tool functions to be assigned to agents\n\n---\n\n## 🤖 Agents Explained in Detail\n\nAgents are defined in `src/agents.py`. Each agent has:\n\n| Attribute | Purpose |\n|-----------|---------|\n| `role` | The job title (what they do) |\n| `goal` | What they're trying to achieve |\n| `backstory` | Their personality/expertise (shapes LLM behavior) |\n| `llm` | Which language model to use |\n| `tools` | List of tools they can use |\n| `verbose` | Whether to print their thinking process |\n\n---\n\n### Agent 1: Search Queries Recommendation Agent"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[],"languages":["python"],"docsSourceLabel":"GITHUB OPENCLEW","editorialOverview":"A hands-on learning project exploring multi-agent AI systems with CrewAI. Build a system that searches, scrapes, and compares product prices using coordinated AI agents. 🤖 ai-agents-product-search: Complete Guide to AI Agents with CrewAI This is a **learning project** to understand how AI Agents work using the **CrewAI** framework. This README explains every concept, every line of code, and every tool in detail. --- 📚 Table of Contents 1. $1 2. $1 3. $1 4. $1 5. $1 6. $1 7. $1 8. $1 9. $1 10. $1 --- 🤖 What is an AI Agent? The Simple Explanation An **AI Agent** is more than just a","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":407,"uniquenessScore":62,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-05-12T06:46:15.815Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-05-12T06:46:15.815Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-10T02:54:21.559Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/github_openclew","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}