From a9e123be079cc5545067c1a02aa5b339ff80859d Mon Sep 17 00:00:00 2001 From: root Date: Thu, 24 Apr 2025 11:55:02 +0200 Subject: [PATCH] Add HuggingFace sentiment analysis skill category --- skills/huggingface/README.md | 19 ++++++++++++ skills/huggingface/__init__.py | 36 +++++++++++++++++++++++ skills/huggingface/base.py | 17 +++++++++++ skills/huggingface/huggingface.jpg | Bin 0 -> 7116 bytes skills/huggingface/schema.json | 23 +++++++++++++++ skills/huggingface/sentiment_analysis.py | 22 ++++++++++++++ 6 files changed, 117 insertions(+) create mode 100644 skills/huggingface/README.md create mode 100644 skills/huggingface/__init__.py create mode 100644 skills/huggingface/base.py create mode 100644 skills/huggingface/huggingface.jpg create mode 100644 skills/huggingface/schema.json create mode 100644 skills/huggingface/sentiment_analysis.py 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 0000000000000000000000000000000000000000..e3bf54068090a264cce355610395e3461d3eff8e GIT binary patch literal 7116 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 VJcWkxh&dsae&-rU0`tAu{{k{wAU*&9 literal 0 HcmV?d00001 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)}" +