diff --git a/skills/huggingface/README.md b/skills/huggingface/README.md new file mode 100644 index 00000000..2e9b9433 --- /dev/null +++ b/skills/huggingface/README.md @@ -0,0 +1,19 @@ +# HuggingFace Skill Category + +This skill category enables natural language understanding tools via HuggingFace Transformers. + +## Included Skills + +### 🧠 Sentiment Analysis + +**Description:** +Analyzes the sentiment of input text (e.g., "I love this!") and returns a label (`POSITIVE`, `NEGATIVE`, etc.) along with a confidence score. + +## Configuration Example + +```yaml +skills: + huggingface: + states: + sentiment_analysis: public + diff --git a/skills/huggingface/__init__.py b/skills/huggingface/__init__.py new file mode 100644 index 00000000..d29b8395 --- /dev/null +++ b/skills/huggingface/__init__.py @@ -0,0 +1,36 @@ +from typing import TypedDict +from abstracts.skill import SkillStoreABC +from skills.base import SkillConfig, SkillState +from skills.huggingface.base import HuggingFaceBaseTool +from skills.huggingface.sentiment_analysis import SentimentAnalysis + +_cache: dict[str, HuggingFaceBaseTool] = {} + +class SkillStates(TypedDict): + sentiment_analysis: SkillState + +class Config(SkillConfig): + states: SkillStates + +async def get_skills( + config: "Config", + is_private: bool, + store: SkillStoreABC, + **_, +) -> list[HuggingFaceBaseTool]: + available_skills = [] + for skill_name, state in config["states"].items(): + if state == "disabled": + continue + elif state == "public" or (state == "private" and is_private): + available_skills.append(skill_name) + + return [get_huggingface_skill(name, store) for name in available_skills] + +def get_huggingface_skill(name: str, store: SkillStoreABC) -> HuggingFaceBaseTool: + if name == "sentiment_analysis": + if name not in _cache: + _cache[name] = SentimentAnalysis(skill_store=store) + return _cache[name] + raise ValueError(f"Unknown HuggingFace skill: {name}") + diff --git a/skills/huggingface/base.py b/skills/huggingface/base.py new file mode 100644 index 00000000..07778191 --- /dev/null +++ b/skills/huggingface/base.py @@ -0,0 +1,17 @@ +from typing import Type +from pydantic import BaseModel, Field +from abstracts.skill import SkillStoreABC +from skills.base import IntentKitSkill + +class HuggingFaceBaseTool(IntentKitSkill): + """Base class for HuggingFace NLP tools.""" + + name: str = Field(description="Tool name") + description: str = Field(description="What the tool does") + args_schema: Type[BaseModel] + skill_store: SkillStoreABC = Field(description="Skill store for data persistence") + + @property + def category(self) -> str: + return "huggingface" + diff --git a/skills/huggingface/huggingface.jpg b/skills/huggingface/huggingface.jpg new file mode 100644 index 00000000..e3bf5406 Binary files /dev/null and b/skills/huggingface/huggingface.jpg differ diff --git a/skills/huggingface/schema.json b/skills/huggingface/schema.json new file mode 100644 index 00000000..f39dc78d --- /dev/null +++ b/skills/huggingface/schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "HuggingFace Skills", + "description": "Configuration schema for HuggingFace-powered skills", + "properties": { + "states": { + "type": "object", + "properties": { + "sentiment_analysis": { + "type": "string", + "title": "Sentiment Analysis", + "enum": ["disabled", "public", "private"], + "description": "State of the sentiment analysis skill" + } + }, + "description": "States for HuggingFace skills" + } + }, + "required": ["states"], + "additionalProperties": true +} + diff --git a/skills/huggingface/sentiment_analysis.py b/skills/huggingface/sentiment_analysis.py new file mode 100644 index 00000000..478f245d --- /dev/null +++ b/skills/huggingface/sentiment_analysis.py @@ -0,0 +1,22 @@ +from typing import Type +from pydantic import BaseModel, Field +from skills.huggingface.base import HuggingFaceBaseTool +from transformers import pipeline + +class SentimentAnalysisInput(BaseModel): + text: str = Field(description="Text to analyze sentiment for") + +class SentimentAnalysis(HuggingFaceBaseTool): + name: str = "sentiment_analysis" + description: str = "Analyze sentiment of a given text using HuggingFace Transformers" + args_schema: Type[BaseModel] = SentimentAnalysisInput + + _analyzer = pipeline("sentiment-analysis") + + async def _arun(self, text: str, **kwargs) -> str: + try: + result = self._analyzer(text)[0] + return f"Sentiment: {result['label']} with score {result['score']:.2f}" + except Exception as e: + return f"Failed to analyze sentiment: {str(e)}" +