Spaces:
Sleeping
Update app.py
Browse files### Detailed Description of `my_custom_tool`
#### Purpose
`my_custom_tool` is a creative and analytical tool designed to generate a fictional psychological portrait of an X (Twitter) user based on their recent online activity. It combines real-world data analysis with imaginative storytelling to produce a poetic, personalized narrative that reflects the user’s inferred personality, emotions, and interests over a specified time period. While originally intended to leverage Grok's native X analysis tools, this version has been adapted for a Hugging Face environment, making it compatible with your existing setup using `smolagents`, `DuckDuckGoSearchTool`, and Hugging Face's NLP models.
The tool aims to:
- **Analyze**: Extract meaningful insights (tone and themes) from publicly available data about an X user.
- **Create**: Transform those insights into a unique, fictionalized psychological profile with a poetic flair.
- **Engage**: Provide an entertaining and thought-provoking output that blends data-driven analysis with artistic expression.
#### Functionality
The tool takes two inputs:
1. **x_username (str)**: The X username of the person to analyze (e.g., "elonmusk").
2. **days_in_past (int)**: The number of days in the past to consider for analysis (limited to 1–30 days).
It performs the following steps:
1. **Input Validation**: Ensures the `days_in_past` value is within the acceptable range (1 to 30). If not, it returns an error message.
2. **Time Range Calculation**: Determines the date range for analysis by calculating the start date (current date minus `days_in_past`) and formatting it as a string (e.g., "from 2025-02-21 to 2025-02-28").
3. **Data Collection**: Since direct access to X posts isn’t available in Hugging Face, it uses `DuckDuckGoSearchTool` to search for recent public snippets related to the user on X (e.g., tweets or mentions). This simulates the collection of activity data.
4. **Text Analysis**:
- **Tone Detection**: Uses a pre-trained Hugging Face sentiment analysis model (`distilbert-base-uncased-finetuned-sst-2-english`) to classify the tone as "positive," "negative," or "neutral" based on the collected text.
- **Theme Extraction**: Employs a zero-shot classification model (`facebook/bart-large-mnli`) to identify dominant themes (e.g., "tech," "humor," "science") from a predefined list of candidate labels.
- **Word Count**: Calculates the total number of words in the collected text to quantify the user’s activity level.
5. **Portrait Generation**: Crafts a poetic narrative based on the analyzed tone, themes, and word count, presenting it as a fictional psychological profile of the user.
6. **Output**: Returns the final portrait as a string, or an error message if no data is found.
#### Implementation Details
The tool is structured into three main functions:
1. **`my_custom_tool(x_username: str, days_in_past: int) -> str`**:
- The main entry point, decorated with `@tool` from `smolagents` to integrate it into your `CodeAgent`.
- Orchestrates the process: validates input, calculates the date range, calls the analysis function, and generates the portrait.
- Handles edge cases, such as no data being found, by returning informative messages.
2. **`analyze_x_activity_with_hf(username: str, days: int) -> dict`**:
- Simulates X activity analysis in a Hugging Face environment:
- Uses `DuckDuckGoSearchTool` to search for snippets with a query like `site:x.com {username} -inurl:(signup | login)`, collecting up to five results.
- Falls back to a generic string if the search fails (e.g., "Recent activity by {username}").
- Analyzes the collected text:
- **Tone**: Applies `sentiment_analyzer` to the first 512 tokens (due to model limits), mapping results to "positive," "negative," or "neutral."
- **Themes**: Uses `topic_classifier` to score a list of candidate labels (`tech`, `politics`, `humor`, etc.), selecting the top two themes with scores above 0.5, or the highest-scoring theme as a fallback.
- **Word Count**: Counts words in the aggregated content.
- Returns a dictionary with the raw content, tone, themes, and word count, or an empty dict if no content is retrieved.
3. **`craft_psychological_portrait(username: str, posts_data: dict, date_range: str) -> str`**:
- Takes the analysis results and constructs a creative narrative.
- Maps the detected tone to one of three poetic styles:
- **Positive**: Describes the user as hopeful and radiant, with words as "sparks of light."
- **Negative**: Portrays a melancholic figure, with words "murmuring like rain."
- **Neutral (or other)**: Presents the user as a curious explorer, with words "unfurling toward the infinite."
- Incorporates the username, date range, themes, and word count into the narrative for personalization.
- Joins the introduction and trait description into a cohesive paragraph.
#### Example Output
For `my_custom_tool("elonmusk", 7)`:
- **Search**: Finds snippets like "Elon Musk tweets about Starship progress."
- **Analysis**: Tone = "positive," themes = ["tech", "science"], word count = 50.
- **Result**:
"@elonmusk, over from 2025-02-21 to 2025-02-28, emerges as a radiant soul, gazing at the world with unyielding hope. Your words weave tech and science into a tapestry of possibility, each of your 50 words a spark of light."
#### Limitations
- **Data Source**: Relies on DuckDuckGo search results rather than direct X API access, which may miss real-time posts or include noise (e.g., unrelated mentions).
- **Text Length**: NLP models are limited to 512 tokens per analysis, truncating longer content.
- **Accuracy**: Sentiment and theme detection depend on the quality of the pre-trained models and the relevance of search results.
#### Why It’s Creative
- **Data-Driven Storytelling**: Merges objective analysis (tone, themes) with subjective, poetic interpretation.
- **Personalization**: Tailors the output to the specific user and time period, making each portrait unique.
- **Hugging Face Fit**: Adapts Grok-like functionality to an open-source environment, leveraging accessible NLP tools creatively.
This tool provides a balance of technical analysis and artistic output, making it both functional and engaging in your Hugging Face-based project.
|
@@ -4,19 +4,98 @@ import requests
|
|
| 4 |
import pytz
|
| 5 |
import yaml
|
| 6 |
from tools.final_answer import FinalAnswerTool
|
|
|
|
| 7 |
|
| 8 |
from Gradio_UI import GradioUI
|
| 9 |
|
| 10 |
# Below is an example of a tool that does nothing. Amaze us with your creativity !
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
@tool
|
| 12 |
-
def my_custom_tool(
|
| 13 |
#Keep this format for the description / args / args description but feel free to modify the tool
|
| 14 |
-
"""A tool that
|
| 15 |
Args:
|
| 16 |
-
|
| 17 |
-
|
| 18 |
"""
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
@tool
|
| 22 |
def get_current_time_in_timezone(timezone: str) -> str:
|
|
@@ -55,7 +134,7 @@ with open("prompts.yaml", 'r') as stream:
|
|
| 55 |
|
| 56 |
agent = CodeAgent(
|
| 57 |
model=model,
|
| 58 |
-
tools=[final_answer],
|
| 59 |
max_steps=6,
|
| 60 |
verbosity_level=1,
|
| 61 |
grammar=None,
|
|
|
|
| 4 |
import pytz
|
| 5 |
import yaml
|
| 6 |
from tools.final_answer import FinalAnswerTool
|
| 7 |
+
from transformers import pipeline # For local NLP analysis
|
| 8 |
|
| 9 |
from Gradio_UI import GradioUI
|
| 10 |
|
| 11 |
# Below is an example of a tool that does nothing. Amaze us with your creativity !
|
| 12 |
+
|
| 13 |
+
# Tools and model already present in the environment
|
| 14 |
+
search_tool = DuckDuckGoSearchTool()
|
| 15 |
+
|
| 16 |
+
# Set up a local NLP pipeline with Hugging Face for text analysis
|
| 17 |
+
sentiment_analyzer = pipeline("sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
|
| 18 |
+
topic_classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
|
| 19 |
+
|
| 20 |
@tool
|
| 21 |
+
def my_custom_tool(x_username: str, days_in_the past:int)-> str: #it's import to specify the return type
|
| 22 |
#Keep this format for the description / args / args description but feel free to modify the tool
|
| 23 |
+
"""A tool that creates a fictional psychological portrait based on an X user's recent activity using Hugging Face tools.
|
| 24 |
Args:
|
| 25 |
+
x_username: The X username to analyze (e.g., 'elonmusk')
|
| 26 |
+
days_in_past: Number of days in the past to analyze (max 30)
|
| 27 |
"""
|
| 28 |
+
# Check if the number of days is within acceptable range
|
| 29 |
+
if days_in_past < 1 or days_in_past > 30:
|
| 30 |
+
return "Please choose a number of days between 1 and 30."
|
| 31 |
+
|
| 32 |
+
# Calculate the time range
|
| 33 |
+
current_date = datetime.datetime.now()
|
| 34 |
+
start_date = current_date - datetime.timedelta(days=days_in_past)
|
| 35 |
+
date_range = f"from {start_date.strftime('%Y-%m-%d')} to {current_date.strftime('%Y-%m-%d')}"
|
| 36 |
+
|
| 37 |
+
# Analyze available data using adapted tools
|
| 38 |
+
posts_data = analyze_x_activity_with_hf(x_username, days_in_past)
|
| 39 |
+
if not posts_data or not posts_data.get("content"):
|
| 40 |
+
return f"No recent activity data found for @{x_username} in the last {days_in_past} days."
|
| 41 |
+
|
| 42 |
+
# Generate the psychological portrait
|
| 43 |
+
portrait = craft_psychological_portrait(x_username, posts_data, date_range)
|
| 44 |
+
return portrait
|
| 45 |
+
|
| 46 |
+
def analyze_x_activity_with_hf(username: str, days: int) -> dict:
|
| 47 |
+
"""Use Hugging Face-compatible tools to analyze X activity."""
|
| 48 |
+
# Search via DuckDuckGo to simulate posts (no direct X access)
|
| 49 |
+
query = f"site:x.com {username} -inurl:(signup | login)"
|
| 50 |
+
try:
|
| 51 |
+
search_results = search_tool(query)
|
| 52 |
+
content = " ".join([result["snippet"] for result in search_results[:5] if result.get("snippet")])
|
| 53 |
+
except Exception:
|
| 54 |
+
content = f"Recent activity by {username}" # Fallback if search fails
|
| 55 |
+
|
| 56 |
+
# Return empty dict if no content is found
|
| 57 |
+
if not content:
|
| 58 |
+
return {}
|
| 59 |
+
|
| 60 |
+
# Analyze tone with DistilBERT
|
| 61 |
+
sentiment = sentiment_analyzer(content[:512])[0] # Limit to 512 tokens
|
| 62 |
+
tone = "positive" if sentiment["label"] == "POSITIVE" else "negative" if sentiment["label"] == "NEGATIVE" else "neutral"
|
| 63 |
+
|
| 64 |
+
# Extract themes with zero-shot classification
|
| 65 |
+
candidate_labels = ["tech", "politics", "humor", "science", "personal", "nature","philosophy"]
|
| 66 |
+
theme_result = topic_classifier(content[:512], candidate_labels, multi_label=False)
|
| 67 |
+
top_themes = [label for label, score in zip(theme_result["labels"], theme_result["scores"]) if score > 0.5][:2]
|
| 68 |
+
if not top_themes:
|
| 69 |
+
top_themes = [theme_result["labels"][0]] # Take the most probable if nothing above 0.5
|
| 70 |
+
|
| 71 |
+
# Count words
|
| 72 |
+
word_count = len(content.split())
|
| 73 |
+
|
| 74 |
+
return {
|
| 75 |
+
"content": content,
|
| 76 |
+
"tone": tone,
|
| 77 |
+
"themes": top_themes,
|
| 78 |
+
"word_count": word_count
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
def craft_psychological_portrait(username: str, posts_data: dict, date_range: str) -> str:
|
| 82 |
+
"""Helper function to craft a fictional psychological portrait."""
|
| 83 |
+
tone = posts_data["tone"]
|
| 84 |
+
themes = " and ".join(posts_data["themes"])
|
| 85 |
+
word_count = posts_data["word_count"]
|
| 86 |
+
|
| 87 |
+
# Generate a creative description based on tone
|
| 88 |
+
if tone == "positive":
|
| 89 |
+
intro = f"@{username}, over {date_range}, emerges as a radiant soul, gazing at the world with unyielding hope."
|
| 90 |
+
trait = f"Your words weave {themes} into a tapestry of possibility, each of your {word_count} words a spark of light."
|
| 91 |
+
elif tone == "negative":
|
| 92 |
+
intro = f"@{username}, across {date_range}, walks a quiet path, shadowed by gentle sorrow."
|
| 93 |
+
trait = f"In {themes}, your {word_count} words murmur like rain, painting a world both tender and lost."
|
| 94 |
+
else: # neutral or other
|
| 95 |
+
intro = f"@{username}, within {date_range}, stands as an explorer of the unknown, eyes wide with wonder."
|
| 96 |
+
trait = f"Your {word_count} words chase {themes}, each a question unfurling toward the infinite."
|
| 97 |
+
|
| 98 |
+
return f"{intro} {trait}"
|
| 99 |
|
| 100 |
@tool
|
| 101 |
def get_current_time_in_timezone(timezone: str) -> str:
|
|
|
|
| 134 |
|
| 135 |
agent = CodeAgent(
|
| 136 |
model=model,
|
| 137 |
+
tools=[final_answer,my_custom_tool, search_tool], # Add compatible HF tools
|
| 138 |
max_steps=6,
|
| 139 |
verbosity_level=1,
|
| 140 |
grammar=None,
|