feat: add new examples from pocketflow-academy
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# Web Search with Analysis
|
||||
|
||||
A web search tool built with PocketFlow that performs searches using SerpAPI and analyzes results using LLM.
|
||||
|
||||
## Features
|
||||
|
||||
- Web search using Google via SerpAPI
|
||||
- Extracts titles, snippets, and links
|
||||
- Analyzes search results using GPT-4 to provide:
|
||||
- Result summaries
|
||||
- Key points/facts
|
||||
- Suggested follow-up queries
|
||||
- Clean command-line interface
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
3. Set required API keys:
|
||||
```bash
|
||||
export SERPAPI_API_KEY='your-serpapi-key'
|
||||
export OPENAI_API_KEY='your-openai-key'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Run the search tool:
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
You will be prompted to:
|
||||
1. Enter your search query
|
||||
2. Specify number of results to fetch (default: 5)
|
||||
|
||||
The tool will then:
|
||||
1. Perform the search using SerpAPI
|
||||
2. Analyze results using GPT-4
|
||||
3. Present a summary with key points and follow-up queries
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
pocketflow-tool-search/
|
||||
├── tools/
|
||||
│ ├── search.py # SerpAPI search functionality
|
||||
│ └── parser.py # Result analysis using LLM
|
||||
├── utils/
|
||||
│ └── call_llm.py # LLM API wrapper
|
||||
├── nodes.py # PocketFlow nodes
|
||||
├── flow.py # Flow configuration
|
||||
├── main.py # Main script
|
||||
└── requirements.txt # Dependencies
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- Requires SerpAPI subscription
|
||||
- Rate limited by both APIs
|
||||
- Basic error handling
|
||||
- Text results only
|
||||
|
||||
## Dependencies
|
||||
|
||||
- pocketflow: Flow-based processing
|
||||
- google-search-results: SerpAPI client
|
||||
- openai: GPT-4 API access
|
||||
- pyyaml: YAML processing
|
||||
@@ -0,0 +1,18 @@
|
||||
from pocketflow import Flow
|
||||
from nodes import SearchNode, AnalyzeResultsNode
|
||||
|
||||
def create_flow() -> Flow:
|
||||
"""Create and configure the search flow
|
||||
|
||||
Returns:
|
||||
Flow: Configured flow ready to run
|
||||
"""
|
||||
# Create nodes
|
||||
search = SearchNode()
|
||||
analyze = AnalyzeResultsNode()
|
||||
|
||||
# Connect nodes
|
||||
search >> analyze
|
||||
|
||||
# Create flow starting with search
|
||||
return Flow(start=search)
|
||||
@@ -0,0 +1,26 @@
|
||||
import os
|
||||
from flow import create_flow
|
||||
|
||||
def main():
|
||||
"""Run the web search flow"""
|
||||
|
||||
# Get search query from user
|
||||
query = input("Enter search query: ")
|
||||
if not query:
|
||||
print("Error: Query is required")
|
||||
return
|
||||
|
||||
# Initialize shared data
|
||||
shared = {
|
||||
"query": query,
|
||||
"num_results": 5
|
||||
}
|
||||
|
||||
# Create and run flow
|
||||
flow = create_flow()
|
||||
flow.run(shared)
|
||||
|
||||
# Results are in shared["analysis"]
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,56 @@
|
||||
from pocketflow import Node
|
||||
from tools.search import SearchTool
|
||||
from tools.parser import analyze_results
|
||||
from typing import List, Dict
|
||||
|
||||
class SearchNode(Node):
|
||||
"""Node to perform web search using SerpAPI"""
|
||||
|
||||
def prep(self, shared):
|
||||
return shared.get("query"), shared.get("num_results", 5)
|
||||
|
||||
def exec(self, inputs):
|
||||
query, num_results = inputs
|
||||
if not query:
|
||||
return []
|
||||
|
||||
searcher = SearchTool()
|
||||
return searcher.search(query, num_results)
|
||||
|
||||
def post(self, shared, prep_res, exec_res):
|
||||
shared["search_results"] = exec_res
|
||||
return "default"
|
||||
|
||||
class AnalyzeResultsNode(Node):
|
||||
"""Node to analyze search results using LLM"""
|
||||
|
||||
def prep(self, shared):
|
||||
return shared.get("query"), shared.get("search_results", [])
|
||||
|
||||
def exec(self, inputs):
|
||||
query, results = inputs
|
||||
if not results:
|
||||
return {
|
||||
"summary": "No search results to analyze",
|
||||
"key_points": [],
|
||||
"follow_up_queries": []
|
||||
}
|
||||
|
||||
return analyze_results(query, results)
|
||||
|
||||
def post(self, shared, prep_res, exec_res):
|
||||
shared["analysis"] = exec_res
|
||||
|
||||
# Print analysis
|
||||
print("\nSearch Analysis:")
|
||||
print("\nSummary:", exec_res["summary"])
|
||||
|
||||
print("\nKey Points:")
|
||||
for point in exec_res["key_points"]:
|
||||
print(f"- {point}")
|
||||
|
||||
print("\nSuggested Follow-up Queries:")
|
||||
for query in exec_res["follow_up_queries"]:
|
||||
print(f"- {query}")
|
||||
|
||||
return "default"
|
||||
@@ -0,0 +1,4 @@
|
||||
pocketflow>=0.1.0
|
||||
google-search-results>=2.4.2 # SerpAPI client
|
||||
openai>=1.0.0 # for search result analysis
|
||||
pyyaml>=6.0.1 # for structured output
|
||||
@@ -0,0 +1,70 @@
|
||||
from typing import Dict, List
|
||||
from utils.call_llm import call_llm
|
||||
|
||||
def analyze_results(query: str, results: List[Dict]) -> Dict:
|
||||
"""Analyze search results using LLM
|
||||
|
||||
Args:
|
||||
query (str): Original search query
|
||||
results (List[Dict]): Search results to analyze
|
||||
|
||||
Returns:
|
||||
Dict: Analysis including summary and key points
|
||||
"""
|
||||
# Format results for prompt
|
||||
formatted_results = []
|
||||
for i, result in enumerate(results, 1):
|
||||
formatted_results.append(f"""
|
||||
Result {i}:
|
||||
Title: {result['title']}
|
||||
Snippet: {result['snippet']}
|
||||
URL: {result['link']}
|
||||
""")
|
||||
|
||||
prompt = f"""
|
||||
Analyze these search results for the query: "{query}"
|
||||
|
||||
{'\n'.join(formatted_results)}
|
||||
|
||||
Please provide:
|
||||
1. A concise summary of the findings (2-3 sentences)
|
||||
2. Key points or facts (up to 5 bullet points)
|
||||
3. Suggested follow-up queries (2-3)
|
||||
|
||||
Output in YAML format:
|
||||
```yaml
|
||||
summary: >
|
||||
brief summary here
|
||||
key_points:
|
||||
- point 1
|
||||
- point 2
|
||||
follow_up_queries:
|
||||
- query 1
|
||||
- query 2
|
||||
```
|
||||
"""
|
||||
|
||||
try:
|
||||
response = call_llm(prompt)
|
||||
# Extract YAML between code fences
|
||||
yaml_str = response.split("```yaml")[1].split("```")[0].strip()
|
||||
|
||||
import yaml
|
||||
analysis = yaml.safe_load(yaml_str)
|
||||
|
||||
# Validate required fields
|
||||
assert "summary" in analysis
|
||||
assert "key_points" in analysis
|
||||
assert "follow_up_queries" in analysis
|
||||
assert isinstance(analysis["key_points"], list)
|
||||
assert isinstance(analysis["follow_up_queries"], list)
|
||||
|
||||
return analysis
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error analyzing results: {str(e)}")
|
||||
return {
|
||||
"summary": "Error analyzing results",
|
||||
"key_points": [],
|
||||
"follow_up_queries": []
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import os
|
||||
from serpapi import GoogleSearch
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
class SearchTool:
|
||||
"""Tool for performing web searches using SerpAPI"""
|
||||
|
||||
def __init__(self, api_key: Optional[str] = None):
|
||||
"""Initialize search tool with API key
|
||||
|
||||
Args:
|
||||
api_key (str, optional): SerpAPI key. Defaults to env var SERPAPI_API_KEY.
|
||||
"""
|
||||
self.api_key = api_key or os.getenv("SERPAPI_API_KEY")
|
||||
if not self.api_key:
|
||||
raise ValueError("SerpAPI key not found. Set SERPAPI_API_KEY env var.")
|
||||
|
||||
def search(self, query: str, num_results: int = 5) -> List[Dict]:
|
||||
"""Perform Google search via SerpAPI
|
||||
|
||||
Args:
|
||||
query (str): Search query
|
||||
num_results (int, optional): Number of results to return. Defaults to 5.
|
||||
|
||||
Returns:
|
||||
List[Dict]: Search results with title, snippet, and link
|
||||
"""
|
||||
# Configure search parameters
|
||||
params = {
|
||||
"engine": "google",
|
||||
"q": query,
|
||||
"api_key": self.api_key,
|
||||
"num": num_results
|
||||
}
|
||||
|
||||
try:
|
||||
# Execute search
|
||||
search = GoogleSearch(params)
|
||||
results = search.get_dict()
|
||||
|
||||
# Extract organic results
|
||||
if "organic_results" not in results:
|
||||
return []
|
||||
|
||||
processed_results = []
|
||||
for result in results["organic_results"][:num_results]:
|
||||
processed_results.append({
|
||||
"title": result.get("title", ""),
|
||||
"snippet": result.get("snippet", ""),
|
||||
"link": result.get("link", "")
|
||||
})
|
||||
|
||||
return processed_results
|
||||
|
||||
except Exception as e:
|
||||
print(f"Search error: {str(e)}")
|
||||
return []
|
||||
@@ -0,0 +1,34 @@
|
||||
import os
|
||||
from openai import OpenAI
|
||||
from pathlib import Path
|
||||
|
||||
# Get the project root directory (parent of utils directory)
|
||||
ROOT_DIR = Path(__file__).parent.parent
|
||||
|
||||
# Initialize OpenAI client with API key from environment
|
||||
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
||||
|
||||
def call_llm(prompt: str) -> str:
|
||||
"""Call OpenAI API to analyze text
|
||||
|
||||
Args:
|
||||
prompt (str): Input prompt for the model
|
||||
|
||||
Returns:
|
||||
str: Model response
|
||||
"""
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-4o",
|
||||
messages=[{"role": "user", "content": prompt}]
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error calling LLM API: {str(e)}")
|
||||
return ""
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test LLM call
|
||||
response = call_llm("What is web search?")
|
||||
print("Response:", response)
|
||||
Reference in New Issue
Block a user