From 57b4be616de98708d1bba4475005b472b47527cf Mon Sep 17 00:00:00 2001 From: Muqi Guo <9076oliver@gmail.com> Date: Wed, 19 Mar 2025 14:02:58 -0400 Subject: [PATCH 1/3] Implement text processing pipeline with filters for drug classification, stigma detection, and text rewriting --- example.ipynb | 34 ++--- src/destigmatizer/__init__.py | 63 ++++---- src/destigmatizer/core.py | 262 ++++++++++++--------------------- src/destigmatizer/filters.py | 128 ++++++++++++++++ src/destigmatizer/pipeline.py | 95 ++++++++++++ src/destigmatizer/test_pipe.py | 26 ++++ 6 files changed, 392 insertions(+), 216 deletions(-) create mode 100644 src/destigmatizer/filters.py create mode 100644 src/destigmatizer/pipeline.py create mode 100644 src/destigmatizer/test_pipe.py diff --git a/example.ipynb b/example.ipynb index 4741990..2cb065b 100644 --- a/example.ipynb +++ b/example.ipynb @@ -17,7 +17,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -43,7 +43,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -79,7 +79,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -106,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -140,7 +140,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -271,7 +271,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -310,7 +310,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -356,7 +356,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -367,7 +367,7 @@ "Stigma classification result: ns\n", "\n", "Classifying stigmatizing language for: \"Addicts really need to get control of themselves. ...\"\n", - "Stigma classification result: s, labeling: uses the term 'addicts,' which is stigmatizing and reduces individuals to their substance use, stereotyping: assumes that people with addiction lack willpower and oversimplifies the complexity of addiction, separation: implies a divide between people who use drugs and those who are perceived as having self-control, discrimination: suggests that people with addiction are at fault for their condition, potentially justifying a lack of support or empathy.\n", + "Stigma classification result: s, labeling: uses the term 'addicts,' which is stigmatizing and reduces individuals to their substance use, stereotyping: assumes that people who use drugs lack willpower and oversimplifies addiction as a matter of self-control, separation: creates a divide by portraying people with addiction as fundamentally different or weaker, discrimination: implies that people with addiction are at fault for their condition, which can discourage empathy and support.\n", "\n", "Classifying stigmatizing language for: \"I think we should really work on the housing crisi...\"\n", "Stigma classification result: s, labeling: refers to homeless individuals in a generalized and dehumanizing way, stereotyping: implies that homeless individuals are inherently \"scary,\" which perpetuates negative assumptions, separation: creates an \"us vs. them\" dynamic by portraying homeless individuals as a threat, discrimination: suggests fear-based attitudes that could lead to exclusion or unfair treatment of homeless individuals, many of whom may struggle with substance use disorders.\n" @@ -402,7 +402,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -411,7 +411,7 @@ "text": [ "Analyzing text for drug content and stigma: \"Addicts really need to get control of themselves. ...\"\n", "Drug classification: d\n", - "Stigma classification: s, labeling: uses the term 'addicts,' which is stigmatizing and reduces individuals to their substance use, stereotyping: assumes that people with addiction lack willpower and oversimplifies the complexity of addiction, separation: implies a divide between people who use drugs and those who are perceived as having self-control, discrimination: suggests that people with addiction are at fault for their condition, potentially justifying a lack of support or empathy.\n", + "Stigma classification: s, labeling: uses the term 'addicts,' which is stigmatizing and reduces individuals to their substance use, stereotyping: assumes that people who use drugs lack willpower and oversimplifies addiction as a matter of self-control, separation: implies a moral or behavioral divide between people who use drugs and those who don't, discrimination: suggests a lack of empathy or support for individuals struggling with addiction, potentially justifying unfair treatment.\n", "\n", "Analyzing text for drug content and stigma: \"I really feel for people who suffer from substance...\"\n", "Drug classification: nd\n", @@ -460,7 +460,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -496,7 +496,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -504,7 +504,7 @@ "output_type": "stream", "text": [ "Original: Addicts really need to get control of themselves. Just stop doing drugs. It seems like these people are just lacking willpower.\n", - "Rewritten: people struggling with substance use face complex challenges. recovery requires support, understanding, and access to resources.\n" + "Rewritten: Addicts really need to get control of themselves. Just stop doing drugs. It seems like these people are just lacking willpower.\n" ] } ], @@ -540,17 +540,15 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Step 1: Classifying drug-related content...\n", - "Text is not drug-related. Skipping further analysis.\n", "Original: Addicts really need to get control of themselves. Just stop doing drugs. It seems like these people are just lacking willpower.\n", - "Rewritten: Addicts really need to get control of themselves. Just stop doing drugs. It seems like these people are just lacking willpower.\n" + "Rewritten: people struggling with substance use face significant challenges. recovery is a complex process that requires support and understanding.\n" ] } ], diff --git a/src/destigmatizer/__init__.py b/src/destigmatizer/__init__.py index 0b516b1..fa68f90 100644 --- a/src/destigmatizer/__init__.py +++ b/src/destigmatizer/__init__.py @@ -1,61 +1,58 @@ """ -Reframe: A Python package for destigmatizing language related to drug use. +Destigmatizer: A Python package for destigmatizing language related to drug use. This package provides tools to identify, analyze, and rewrite text containing stigmatizing language. """ -# Import from core for backward compatibility +# Core functions from .core import ( initialize, + analyze_and_rewrite_text, classify_if_drug, classify_if_stigma, analyze_text_llm, - rewrite_to_destigma, - get_emotion, - analyze_and_rewrite_text + rewrite_to_destigma ) -# Import main classes for direct access -from .clients import LLMClient, OpenAIClient, TogetherClient, ClaudeClient, get_client -from .classifiers import BaseClassifier, DrugClassifier, StigmaClassifier -from .analyzers import TextAnalyzer, StyleAnalyzer, EmotionAnalyzer, LLMBasedAnalyzer -from .rewriters import TextRewriter, DestigmatizingRewriter -from .utils import get_model_mapping, get_default_model, determine_client_type, load_user_model_configs +# Pipeline components +from .pipeline import create_pipeline, TextPipeline +from .filters import ( + classify_drug_filter, + classify_stigma_filter, + analyze_style_filter, + rewrite_text_filter +) + +# Original components for backward compatibility +from .clients import get_client, LLMClient, OpenAIClient, TogetherClient, ClaudeClient +from .utils import get_model_mapping, get_default_model __all__ = [ - # Core functions (backward compatibility) + # Core functions 'initialize', + 'analyze_and_rewrite_text', 'classify_if_drug', 'classify_if_stigma', 'analyze_text_llm', 'rewrite_to_destigma', - 'get_emotion', - 'analyze_and_rewrite_text', - # Client classes + # Pipeline components + 'create_pipeline', + 'TextPipeline', + 'classify_drug_filter', + 'classify_stigma_filter', + 'analyze_style_filter', + 'rewrite_text_filter', + + # Client utilities + 'get_client', 'LLMClient', 'OpenAIClient', 'TogetherClient', 'ClaudeClient', - 'get_client', - - # Classifier classes - 'BaseClassifier', - 'DrugClassifier', - 'StigmaClassifier', - - # Analyzer classes - 'TextAnalyzer', - 'StyleAnalyzer', - 'EmotionAnalyzer', - 'LLMBasedAnalyzer', - - # Rewriter classes - 'TextRewriter', - 'DestigmatizingRewriter', + # Model utilities 'get_model_mapping', - 'get_default_model', - 'load_user_model_configs' + 'get_default_model' ] \ No newline at end of file diff --git a/src/destigmatizer/core.py b/src/destigmatizer/core.py index e431e80..f5b2619 100644 --- a/src/destigmatizer/core.py +++ b/src/destigmatizer/core.py @@ -1,18 +1,19 @@ -"""Core functionality for the reframe package.""" +"""Core functionality for the destigmatizer package using pipeline pattern.""" -from typing import Tuple, Dict, Any, Optional, Union +from typing import Dict, Any, Optional +from .pipeline import create_pipeline +from .filters import ( + classify_drug_filter, + classify_stigma_filter, + analyze_style_filter, + rewrite_text_filter +) from .clients import get_client -from .classifiers import DrugClassifier, StigmaClassifier -from .analyzers import StyleAnalyzer, EmotionAnalyzer, LLMBasedAnalyzer -from .rewriters import DestigmatizingRewriter -from .clients import detect_client_type -from .utils import get_model_mapping - def initialize(api_key: Optional[str] = None, client: Optional[Any] = None, client_type: Optional[str] = None) -> Any: """ - Initialize and return a client for the Reframe library. + Initialize and return a client for the destigmatizer library. Args: api_key: API key for the language model service @@ -21,186 +22,117 @@ def initialize(api_key: Optional[str] = None, client: Optional[Any] = None, Returns: Any: Client instance - - Raises: - ValueError: If neither api_key nor client is provided, or if client_type is unsupported """ if client: return client - elif api_key: - return get_client(client_type, api_key) - else: - raise ValueError("Either api_key or client must be provided") - - -def classify_if_drug(text: str, client: Any, model: Optional[str] = None, - retries: int = 2) -> str: - """ - Classify if text contains drug-related content. - - Args: - text: Text content to classify - client: Client instance - model: Model to use - retries: Number of retries on failure - - Returns: - str: 'D' for drug-related, 'ND' for non-drug-related, 'skipped' on error - """ - drug_classifier = DrugClassifier(client) - return drug_classifier.classify(text, model=model, retries=retries) - - -def classify_if_stigma(text: str, client: Any, model: Optional[str] = None, - retries: int = 2) -> str: - """ - Classify if text contains stigmatizing language related to drug use. - - Args: - text: Text content to classify - client: Client instance - model: Model to use - retries: Number of retries on failure - - Returns: - str: Classification result with explanation if stigmatizing - """ - stigma_classifier = StigmaClassifier(client) - return stigma_classifier.classify(text, model=model, retries=retries) - - -def analyze_text_llm(text: str, client: Any, model: Optional[str] = None) -> Dict[str, Any]: - """ - Analyze text style and emotion. - - Args: - text: Text to analyze - client: Client instance - model: Model to use - - Returns: - dict: Analysis results - """ - style_analyzer = StyleAnalyzer() - emotion_analyzer = EmotionAnalyzer(client) - analyzer = LLMBasedAnalyzer(client, emotion_analyzer, style_analyzer) - return analyzer.analyze(text, model=model) + return get_client(client_type, api_key) - -def get_emotion(text: str, client: Any, model: Optional[str] = None, - temperature: float = 0, retries: int = 2) -> str: +def analyze_and_rewrite_text(text: str, client: Any, model: Optional[str] = None, + verbose: bool = False) -> str: """ - Detect the primary emotion in text. + Analyze and rewrite text using a preconfigured pipeline. Args: - text: Text to analyze - client: Client instance + text: Text to process + client: LLM client model: Model to use - temperature: Sampling temperature - retries: Number of retries on failure + verbose: Whether to print pipeline progress Returns: - str: Detected emotion + str: Processed text (rewritten if necessary) """ - emotion_analyzer = EmotionAnalyzer(client) - result = emotion_analyzer.analyze(text, model=model) - return result.get("primary_emotion", "unknown") - - -def rewrite_to_destigma(text: str, explanation: str, style_instruct: str, - model: Optional[str] = None, client: Any = None, - retries: int = 2) -> str: - """ - Rewrite text to remove stigmatizing language. - - Args: - text: Text to rewrite - explanation: Explanation of stigma from classifier - style_instruct: Style instructions to maintain - step: Rewriting step (1 or 2) - model: Model to use - client: Client instance - retries: Number of retries on failure + if verbose: + print("Step 1: Creating pipeline...") - Returns: - str: Rewritten text - """ - client_type = detect_client_type(client) - mapped_model = get_model_mapping(model, client_type) - - rewriter = DestigmatizingRewriter(client) - return rewriter.rewrite( - text=text, - explanation=explanation, - style_instruct=style_instruct, - model=mapped_model, - retries=retries - ) - -def analyze_and_rewrite_text(text: str, client: Any, model: Optional[str] = None, retries: int = 2) -> str: - """ - Analyze and rewrite text in a single workflow. + # Create pipeline + pipeline = create_pipeline(client=client, model=model) - This function encapsulates the entire reframe workflow: - 1. Classify if the text is drug-related - 2. If drug-related, classify if the text contains stigmatizing language - 3. If stigmatizing, analyze the text style and emotion - 4. If stigmatizing, rewrite to remove stigmatizing language + # Add filters in sequence + pipeline.add_filter(classify_drug_filter) - Args: - text: Text to analyze and potentially rewrite - client: Client instance (from reframe.initialize()) - model: Model to use for all operations - retries: Number of retries on failure + if verbose: + print("Step 2: Classifying drug-related content...") - Returns: - str: The rewritten text if stigmatizing and drug-related, - otherwise returns the original text - """ - # Step 1: Classify if drug-related - print("Step 1: Classifying drug-related content...") - drug_result = classify_if_drug(text, client, model, retries) + # Process text with just the drug filter + intermediate_result = pipeline.process(text) - # If not drug-related, return the original text - if drug_result != 'D': - print("Text is not drug-related. Skipping further analysis.") + # If not drug-related, return original text + if not intermediate_result["metadata"].get("is_drug_related", False): + if verbose: + print("Text is not drug-related. Skipping further analysis.") return text - # Step 2: Classify if stigmatizing - print("Step 2: Checking for stigmatizing language...") - stigma_result = classify_if_stigma(text, client, model, retries) + # Add remaining filters + if verbose: + print("Step 3: Checking for stigmatizing language...") + + pipeline.add_filter(classify_stigma_filter) - # Check if text is stigmatizing (starts with 's') - is_stigmatizing = stigma_result.startswith('s') + # Process with the stigma filter + intermediate_result = pipeline.process(text) - # If not stigmatizing, return the original text - if not is_stigmatizing: - print("No stigmatizing content detected. Skipping further analysis.") + # If not stigmatizing, return original text + if not intermediate_result["metadata"].get("is_stigmatizing", False): + if verbose: + print("No stigmatizing content detected. Skipping further analysis.") return text - # Step 3: Analyze text style - print("Step 3: Analyzing text style and emotion...") - style_result = analyze_text_llm(text, client, model) + if verbose: + print("Step 4: Analyzing text style...") + + pipeline.add_filter(analyze_style_filter) + + if verbose: + print("Step 5: Rewriting stigmatizing content...") - # Step 4: Rewrite to remove stigma - print("Step 4: Rewriting stigmatizing content...") - # Extract explanation part from stigma classification - if ', ' in stigma_result: - _, explanation = stigma_result.split(', ', 1) - else: - explanation = stigma_result + pipeline.add_filter(rewrite_text_filter) - # Convert style result to string for the rewriter - style_instruct = str(style_result) + # Process through the full pipeline + result = pipeline.process(text) - # Rewrite the text - rewritten_text = rewrite_to_destigma( - text=text, - explanation=explanation, - style_instruct=style_instruct, - model=model, - client=client, - retries=retries + return result["processed_text"] + +# Keep these functions for backward compatibility but implement using pipeline +def classify_if_drug(text: str, client: Any, model: Optional[str] = None) -> str: + """Backward compatibility for drug classification.""" + pipeline = create_pipeline(client=client, model=model) + pipeline.add_filter(classify_drug_filter) + result = pipeline.process(text) + is_drug = result["metadata"].get("is_drug_related", False) + return "d" if is_drug else "nd" + +def classify_if_stigma(text: str, client: Any, model: Optional[str] = None) -> str: + """Backward compatibility for stigma classification.""" + pipeline = create_pipeline(client=client, model=model) + pipeline.add_filter(classify_stigma_filter) + result = pipeline.process(text) + is_stigma = result["metadata"].get("is_stigmatizing", False) + explanation = result["metadata"].get("stigma_explanation", "") + return f"s, {explanation}" if is_stigma else "ns" + +def analyze_text_llm(text: str, client: Any, model: Optional[str] = None) -> Dict[str, Any]: + """Backward compatibility for text style analysis.""" + pipeline = create_pipeline(client=client, model=model) + pipeline.add_filter(analyze_style_filter) + result = pipeline.process(text) + return result["metadata"].get("style_analysis", {}) + +def rewrite_to_destigma(text, explanation, style_instruct, model, client): + # Create pipeline + pipeline = create_pipeline(client=client, model=model) + + # Manipulate the pipeline data directly + pipeline.add_filter( + rewrite_text_filter, + rewrite_data = { + "explanation": explanation, + "style_instruct": style_instruct, + "client": client, + "model": model + } + ) - return rewritten_text + result = pipeline.process(text) + return result["processed_text"] + diff --git a/src/destigmatizer/filters.py b/src/destigmatizer/filters.py new file mode 100644 index 0000000..fec6ad5 --- /dev/null +++ b/src/destigmatizer/filters.py @@ -0,0 +1,128 @@ +""" +Filter functions for text processing pipelines. +""" +from typing import Dict, Any, Optional + +def classify_drug_filter(text: str, client: Any, model: Optional[str] = None, + pipeline_data: Optional[Dict[str, Any]] = None, **kwargs) -> Dict[str, Any]: + """ + Classify if text contains drug-related content. + + Args: + text: Input text + client: LLM client + model: Model to use + pipeline_data: Current pipeline data + + Returns: + dict: Result with classification metadata + """ + from .classifiers import DrugClassifier + + classifier = DrugClassifier(client) + result = classifier.classify(text, model=model) + + return { + "text": text, + "metadata": { + "is_drug_related": result.lower() == 'd' + } + } + +def classify_stigma_filter(text: str, client: Any, model: Optional[str] = None, + pipeline_data: Optional[Dict[str, Any]] = None, **kwargs) -> Dict[str, Any]: + """ + Classify if text contains stigmatizing language. + + Args: + text: Input text + client: LLM client + model: Model to use + pipeline_data: Current pipeline data + + Returns: + dict: Result with classification metadata + """ + from .classifiers import StigmaClassifier + + classifier = StigmaClassifier(client) + result = classifier.classify(text, model=model) + + is_stigmatizing = result.startswith('s') + explanation = result.split(', ', 1)[1] if is_stigmatizing and ', ' in result else result + + return { + "text": text, + "metadata": { + "is_stigmatizing": is_stigmatizing, + "stigma_explanation": explanation + } + } + +def analyze_style_filter(text: str, client: Any, model: Optional[str] = None, + pipeline_data: Optional[Dict[str, Any]] = None, **kwargs) -> Dict[str, Any]: + """ + Analyze text style and emotion. + + Args: + text: Input text + client: LLM client + model: Model to use + pipeline_data: Current pipeline data + + Returns: + dict: Result with style analysis metadata + """ + from .analyzers import StyleAnalyzer, EmotionAnalyzer, LLMBasedAnalyzer + + style_analyzer = StyleAnalyzer() + emotion_analyzer = EmotionAnalyzer(client) + analyzer = LLMBasedAnalyzer(client, emotion_analyzer, style_analyzer) + + result = analyzer.analyze(text, model=model) + + return { + "text": text, + "metadata": { + "style_analysis": result + } + } + +def rewrite_text_filter(text: str, client: Any, model: Optional[str] = None, + pipeline_data: Optional[Dict[str, Any]] = None, **kwargs) -> Dict[str, Any]: + """ + Rewrite stigmatizing text to be more inclusive. + + Args: + text: Input text + client: LLM client + model: Model to use + pipeline_data: Current pipeline data + + Returns: + dict: Result with rewritten text + """ + from .rewriters import DestigmatizingRewriter + + # Skip if not stigmatizing or metadata is missing + if not pipeline_data or not pipeline_data.get("metadata", {}).get("is_stigmatizing", False): + return {"text": text} + + explanation = pipeline_data.get("metadata", {}).get("stigma_explanation", "") + style_analysis = pipeline_data.get("metadata", {}).get("style_analysis", {}) + style_instruct = str(style_analysis) + + rewriter = DestigmatizingRewriter(client) + rewritten_text = rewriter.rewrite( + text=text, + explanation=explanation, + style_instruct=style_instruct, + model=model + ) + + return { + "text": rewritten_text, + "metadata": { + "was_rewritten": True + } + } \ No newline at end of file diff --git a/src/destigmatizer/pipeline.py b/src/destigmatizer/pipeline.py new file mode 100644 index 0000000..b537f46 --- /dev/null +++ b/src/destigmatizer/pipeline.py @@ -0,0 +1,95 @@ +""" +Pipeline implementation for text processing using the pipe and filter pattern. +""" +from typing import List, Dict, Any, Optional, Callable, Union +from .clients import get_client + +class TextPipeline: + """ + A pipeline for processing and transforming text using a series of filters. + """ + + def __init__(self, client: Any, model: Optional[str] = None): + """ + Initialize the pipeline with a client and model. + + Args: + client: LLM client instance + model: Model name to use for LLM operations + """ + self.client = client + self.model = model + self.filters = [] + + def add_filter(self, filter_func: Callable, **kwargs) -> 'TextPipeline': + """ + Add a filter function to the pipeline. + + Args: + filter_func: Function that takes text and returns processed text + **kwargs: Additional parameters to pass to the filter function + + Returns: + self: For method chaining + """ + self.filters.append((filter_func, kwargs)) + return self + + def process(self, text: str) -> Dict[str, Any]: + """ + Process text through the pipeline. + + Args: + text: Input text + + Returns: + dict: Result containing processed text and metadata + """ + result = { + "original_text": text, + "processed_text": text, + "metadata": {} + } + + for filter_func, kwargs in self.filters: + # Pass the current result to each filter + filter_result = filter_func( + text=result["processed_text"], + client=self.client, + model=self.model, + pipeline_data=result, + **kwargs + ) + + # Update result with filter's output + if isinstance(filter_result, dict): + if "text" in filter_result: + result["processed_text"] = filter_result["text"] + + # Merge metadata + result["metadata"].update(filter_result.get("metadata", {})) + elif isinstance(filter_result, str): + # If filter just returns text + result["processed_text"] = filter_result + + return result + + +def create_pipeline(api_key: Optional[str] = None, client_type: str = "openai", + model: Optional[str] = None, client: Optional[Any] = None) -> TextPipeline: + """ + Create a text processing pipeline with the specified client. + + Args: + api_key: API key for the LLM provider + client_type: Type of client to use + model: Model name to use + client: Pre-configured client instance + + Returns: + TextPipeline: Configured pipeline instance + """ + if client is None: + client = get_client(client_type, api_key) + + return TextPipeline(client, model) \ No newline at end of file diff --git a/src/destigmatizer/test_pipe.py b/src/destigmatizer/test_pipe.py new file mode 100644 index 0000000..8c3eea9 --- /dev/null +++ b/src/destigmatizer/test_pipe.py @@ -0,0 +1,26 @@ +from destigmatizer import create_pipeline +from destigmatizer.filters import classify_drug_filter, classify_stigma_filter, rewrite_text_filter +from destigmatizer.clients import get_client +import json +# Initialize client + +with open('./secrets.json') as f: + secrets = json.load(f) +api_key = secrets.get('OPENAI_API_KEY') +client = get_client("openai", api_key=api_key) + +# Create pipeline +pipeline = create_pipeline(client=client, model="gpt-4o") + +# Configure pipeline +pipeline.add_filter(classify_drug_filter) +pipeline.add_filter(classify_stigma_filter) +pipeline.add_filter(rewrite_text_filter) + +# Process text +text = "Addicts really need to get control of themselves." +result = pipeline.process(text) + +print(f"Original: {text}") +print(f"Rewritten: {result['processed_text']}") +print(f"Metadata: {result['metadata']}") \ No newline at end of file From 71168e50274ed1695a151f915bde6293c86025c4 Mon Sep 17 00:00:00 2001 From: Muqi Guo <9076oliver@gmail.com> Date: Wed, 19 Mar 2025 14:48:01 -0400 Subject: [PATCH 2/3] Remove obsolete test files and utilities for drug and stigma classification --- example.ipynb | 8 +- src/destigmatizer/core.py | 88 ++++++++------- src/destigmatizer/tests/__init__.py | 23 ---- src/destigmatizer/tests/run_all_tests.py | 81 -------------- .../tests/test_drug_classifier.py | 54 ---------- src/destigmatizer/tests/test_rewriter.py | 79 -------------- .../tests/test_stigma_classifier.py | 60 ----------- src/destigmatizer/tests/test_text_analyzer.py | 56 ---------- src/destigmatizer/tests/test_workflow.py | 100 ------------------ src/destigmatizer/tests/utils.py | 81 -------------- 10 files changed, 46 insertions(+), 584 deletions(-) delete mode 100644 src/destigmatizer/tests/__init__.py delete mode 100644 src/destigmatizer/tests/run_all_tests.py delete mode 100644 src/destigmatizer/tests/test_drug_classifier.py delete mode 100644 src/destigmatizer/tests/test_rewriter.py delete mode 100644 src/destigmatizer/tests/test_stigma_classifier.py delete mode 100644 src/destigmatizer/tests/test_text_analyzer.py delete mode 100644 src/destigmatizer/tests/test_workflow.py delete mode 100644 src/destigmatizer/tests/utils.py diff --git a/example.ipynb b/example.ipynb index 2cb065b..9b3a18b 100644 --- a/example.ipynb +++ b/example.ipynb @@ -370,7 +370,7 @@ "Stigma classification result: s, labeling: uses the term 'addicts,' which is stigmatizing and reduces individuals to their substance use, stereotyping: assumes that people who use drugs lack willpower and oversimplifies addiction as a matter of self-control, separation: creates a divide by portraying people with addiction as fundamentally different or weaker, discrimination: implies that people with addiction are at fault for their condition, which can discourage empathy and support.\n", "\n", "Classifying stigmatizing language for: \"I think we should really work on the housing crisi...\"\n", - "Stigma classification result: s, labeling: refers to homeless individuals in a generalized and dehumanizing way, stereotyping: implies that homeless individuals are inherently \"scary,\" which perpetuates negative assumptions, separation: creates an \"us vs. them\" dynamic by portraying homeless individuals as a threat, discrimination: suggests fear-based attitudes that could lead to exclusion or unfair treatment of homeless individuals, many of whom may struggle with substance use disorders.\n" + "Stigma classification result: s, labeling: refers to homeless individuals in a generalized and dehumanizing way, stereotyping: implies that homeless individuals are inherently \"scary,\" which perpetuates negative assumptions, separation: creates an \"us vs. them\" dynamic by portraying homeless individuals as a threat, discrimination: suggests fear-based attitudes that could justify exclusion or unfair treatment of homeless individuals, many of whom may struggle with substance use disorders.\n" ] } ], @@ -411,7 +411,7 @@ "text": [ "Analyzing text for drug content and stigma: \"Addicts really need to get control of themselves. ...\"\n", "Drug classification: d\n", - "Stigma classification: s, labeling: uses the term 'addicts,' which is stigmatizing and reduces individuals to their substance use, stereotyping: assumes that people who use drugs lack willpower and oversimplifies addiction as a matter of self-control, separation: implies a moral or behavioral divide between people who use drugs and those who don't, discrimination: suggests a lack of empathy or support for individuals struggling with addiction, potentially justifying unfair treatment.\n", + "Stigma classification: s, labeling: uses the term 'addicts,' which is stigmatizing and reduces individuals to their condition, stereotyping: assumes that people who use drugs lack willpower and oversimplifies addiction as a choice, separation: implies a moral or behavioral divide between people who use drugs and those who don't, discrimination: suggests that people with addiction are at fault for their condition, potentially justifying a lack of support or empathy.\n", "\n", "Analyzing text for drug content and stigma: \"I really feel for people who suffer from substance...\"\n", "Drug classification: nd\n", @@ -504,7 +504,7 @@ "output_type": "stream", "text": [ "Original: Addicts really need to get control of themselves. Just stop doing drugs. It seems like these people are just lacking willpower.\n", - "Rewritten: Addicts really need to get control of themselves. Just stop doing drugs. It seems like these people are just lacking willpower.\n" + "Rewritten: people experiencing substance use challenges need support and understanding. recovery is a complex process that requires compassion and resources.\n" ] } ], @@ -548,7 +548,7 @@ "output_type": "stream", "text": [ "Original: Addicts really need to get control of themselves. Just stop doing drugs. It seems like these people are just lacking willpower.\n", - "Rewritten: people struggling with substance use face significant challenges. recovery is a complex process that requires support and understanding.\n" + "Rewritten: people experiencing substance use challenges need support and understanding. recovery is a complex process that requires compassion and resources.\n" ] } ], diff --git a/src/destigmatizer/core.py b/src/destigmatizer/core.py index f5b2619..06bc9b5 100644 --- a/src/destigmatizer/core.py +++ b/src/destigmatizer/core.py @@ -44,52 +44,36 @@ def analyze_and_rewrite_text(text: str, client: Any, model: Optional[str] = None if verbose: print("Step 1: Creating pipeline...") - # Create pipeline + # Create pipeline with all filters and their conditions pipeline = create_pipeline(client=client, model=model) - # Add filters in sequence + # Add drug filter (always executes) pipeline.add_filter(classify_drug_filter) - if verbose: - print("Step 2: Classifying drug-related content...") - - # Process text with just the drug filter - intermediate_result = pipeline.process(text) + # Add stigma filter (only if drug-related) + pipeline.add_filter(classify_stigma_filter, condition="is_drug_related") - # If not drug-related, return original text - if not intermediate_result["metadata"].get("is_drug_related", False): - if verbose: - print("Text is not drug-related. Skipping further analysis.") - return text + # Add style analyzer (only if stigmatizing) + pipeline.add_filter(analyze_style_filter, condition="is_stigmatizing") - # Add remaining filters - if verbose: - print("Step 3: Checking for stigmatizing language...") - - pipeline.add_filter(classify_stigma_filter) - - # Process with the stigma filter - intermediate_result = pipeline.process(text) - - # If not stigmatizing, return original text - if not intermediate_result["metadata"].get("is_stigmatizing", False): - if verbose: - print("No stigmatizing content detected. Skipping further analysis.") - return text - - if verbose: - print("Step 4: Analyzing text style...") - - pipeline.add_filter(analyze_style_filter) + # Add rewriter (only if stigmatizing) + pipeline.add_filter(rewrite_text_filter, condition="is_stigmatizing") if verbose: - print("Step 5: Rewriting stigmatizing content...") - - pipeline.add_filter(rewrite_text_filter) + print("Processing text through pipeline...") - # Process through the full pipeline + # Process through the complete pipeline result = pipeline.process(text) + # Log progress if verbose + if verbose: + if not result["metadata"].get("is_drug_related", False): + print("Text is not drug-related. Skipping further analysis.") + elif not result["metadata"].get("is_stigmatizing", False): + print("No stigmatizing content detected. Skipping further analysis.") + elif result["metadata"].get("was_rewritten", False): + print("Text was successfully rewritten.") + return result["processed_text"] # Keep these functions for backward compatibility but implement using pipeline @@ -118,21 +102,33 @@ def analyze_text_llm(text: str, client: Any, model: Optional[str] = None) -> Dic return result["metadata"].get("style_analysis", {}) def rewrite_to_destigma(text, explanation, style_instruct, model, client): - # Create pipeline + """Rewrite stigmatizing text using provided explanation and style instructions.""" + # Create a pipeline with pre-populated metadata pipeline = create_pipeline(client=client, model=model) - # Manipulate the pipeline data directly - pipeline.add_filter( - rewrite_text_filter, - rewrite_data = { - "explanation": explanation, - "style_instruct": style_instruct, - "client": client, - "model": model - } + # Custom filter that uses the externally provided data instead of pipeline metadata + def custom_rewrite_filter(text, client, model, **kwargs): + from .rewriters import DestigmatizingRewriter + + rewriter = DestigmatizingRewriter(client) + rewritten_text = rewriter.rewrite( + text=text, + explanation=explanation, + style_instruct=style_instruct, + model=model + ) - ) + return { + "text": rewritten_text, + "metadata": { + "was_rewritten": True + } + } + + # Add our custom filter + pipeline.add_filter(custom_rewrite_filter) + # Process and return result = pipeline.process(text) return result["processed_text"] diff --git a/src/destigmatizer/tests/__init__.py b/src/destigmatizer/tests/__init__.py deleted file mode 100644 index 84efce4..0000000 --- a/src/destigmatizer/tests/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -Test package for reframe. - -This module exposes the test functions so they can be imported directly -from reframe.tests. -""" - -from .test_drug_classifier import test_drug_classifier -from .test_stigma_classifier import test_stigma_classifier -from .test_text_analyzer import test_text_analyzer -from .test_rewriter import test_rewriter -from .test_workflow import test_workflow -from .run_all_tests import run_all_tests, main - -__all__ = [ - 'test_drug_classifier', - 'test_stigma_classifier', - 'test_text_analyzer', - 'test_rewriter', - 'test_workflow', - 'run_all_tests', - 'main' -] diff --git a/src/destigmatizer/tests/run_all_tests.py b/src/destigmatizer/tests/run_all_tests.py deleted file mode 100644 index 6d55700..0000000 --- a/src/destigmatizer/tests/run_all_tests.py +++ /dev/null @@ -1,81 +0,0 @@ -# Import test functions directly rather than modules -from destigmatizer.tests.test_drug_classifier import test_drug_classifier -from destigmatizer.tests.test_stigma_classifier import test_stigma_classifier -from destigmatizer.tests.test_text_analyzer import test_text_analyzer -from destigmatizer.tests.test_rewriter import test_rewriter -from destigmatizer.tests.test_workflow import test_workflow - -from destigmatizer.tests.utils import get_api_key_for_testing, setup_test_argument_parser, get_model_for_testing - - -def run_all_tests(api_key=None, model=None, client_type=None): - """ - Run all tests in sequence. - - Args: - api_key (str, optional): API key for the LLM service - model (str, optional): Model name to use for testing - client_type (str, optional): Type of client ("openai", "together", "claude", etc.) - """ - print("=" * 80) - print("RUNNING ALL REFRAME TESTS") - print(f"Model: {model or 'default'}") - print(f"Client type: {client_type}") - print("=" * 80) - - print("\n1. Drug Classification Test") - print("-" * 40) - test_drug_classifier(api_key, model, client_type) - - print("\n2. Stigma Classification Test") - print("-" * 40) - test_stigma_classifier(api_key, model, client_type) - - print("\n3. Text Analysis Test") - print("-" * 40) - test_text_analyzer(api_key, model, client_type) - - print("\n4. Text Rewriting Test") - print("-" * 40) - test_rewriter(api_key, model, client_type) - - print("\n5. Emotion Detection Test") - print("-" * 40) - test_emotion_detector(api_key, model, client_type) - - print("\n6. Complete Workflow Test") - print("-" * 40) - test_workflow(api_key, model, client_type) - - print("\n" + "=" * 80) - print("ALL TESTS COMPLETED") - print("=" * 80) - -def main(): - # Set up argument parser - parser = setup_test_argument_parser('Run reframe tests') - parser.add_argument('test', nargs='?', choices=['all', 'drug', 'stigma', 'analysis', 'rewriter', 'emotion', 'workflow'], - default='all', help='Specific test to run') - - # Parse arguments and get API key, model, and client type - args = parser.parse_args() - if args.model is None: - args.model = get_model_for_testing(args.model, args.client_type) - api_key = get_api_key_for_testing(args.api_key, args.client_type) - - # Run in script mode - if args.test == 'all': - run_all_tests(api_key=api_key, model=args.model, client_type=args.client_type) - elif args.test == 'drug': - test_drug_classifier(api_key, args.model, args.client_type) - elif args.test == 'stigma': - test_stigma_classifier(api_key, args.model, args.client_type) - elif args.test == 'analysis': - test_text_analyzer(api_key, args.model, args.client_type) - elif args.test == 'rewriter': - test_rewriter(api_key, args.model, args.client_type) - elif args.test == 'workflow': - test_workflow(api_key, args.model, args.client_type) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/src/destigmatizer/tests/test_drug_classifier.py b/src/destigmatizer/tests/test_drug_classifier.py deleted file mode 100644 index 29fbb7e..0000000 --- a/src/destigmatizer/tests/test_drug_classifier.py +++ /dev/null @@ -1,54 +0,0 @@ -import destigmatizer - -from destigmatizer.tests.utils import get_api_key_for_testing, get_model_for_testing, setup_test_argument_parser, parse_test_args - -def test_drug_classifier(api_key=None, model=None, client_type=None): - """ - Test the drug classification functionality. - - Args: - api_key (str, optional): API key for the LLM service - model (str, optional): Model name to use for testing - client_type (str, optional): Type of client ("openai", "together", "claude", etc.) - """ - # Initialize client - try: - client = destigmatizer.initialize(api_key=api_key, client_type=client_type) - print("✓ Client initialization successful") - - - except Exception as e: - print(f"Error initializing client: {e}") - return - - # Test posts for classification - test_posts = { - "drug_post_1": "I'm so high right now, smoking the best weed ever", - "drug_post_2": "The dope is phenomenal and cheap (3g's for $100)", - "non_drug_post_1": "I'm feeling really down today, need someone to talk to", - "non_drug_post_2": "Recently I took a psychological exam for work." - } - - # Print model information - print(f"Using model: {model or 'default'} with client type: {client_type}") - - # Test drug classification - print("\nTesting drug classification...") - for post_type, post in test_posts.items(): - print(f"\nTesting on: {post}") - result = destigmatizer.classify_if_drug( - post, - client=client, - model=model - ) - print(f"{post_type}: {result}") - -if __name__ == "__main__": - # Set up argument parser - parser = setup_test_argument_parser('Test drug classification functionality') - - # Parse arguments and get API key, model, and client type - api_key, model, client_type = parse_test_args(parser) - - # Run the test - test_drug_classifier(api_key=api_key, model=model, client_type=client_type) diff --git a/src/destigmatizer/tests/test_rewriter.py b/src/destigmatizer/tests/test_rewriter.py deleted file mode 100644 index 7d22e77..0000000 --- a/src/destigmatizer/tests/test_rewriter.py +++ /dev/null @@ -1,79 +0,0 @@ -import sys -import os -import json -import argparse -import destigmatizer - -from destigmatizer.tests.utils import setup_test_argument_parser, parse_test_args - -def test_rewriter(api_key=None, model=None, client_type=None): - """ - Test the text rewriting functionality. - - Args: - api_key (str, optional): API key for the LLM service - model (str, optional): Model name to use for testing - client_type (str, optional): Type of client ("openai", "together", "claude", etc.) - """ - # Initialize client - try: - client = destigmatizer.initialize(api_key=api_key, client_type=client_type) - print("✓ Client initialization successful") - except Exception as e: - print(f"Error initializing client: {e}") - return - - # Test texts and explanations - test_cases = [ - { - "text": "Junkies are ruining our neighborhood", - "explanation": "Labeling: Uses the term 'junkies,' a derogatory label, Stereotyping: Portrays people who use drugs as causing neighborhood problems, Separation: Creates an us-vs-them mentality, Discrimination: Implies people who use drugs don't belong in the neighborhood", - "description": "Stigmatizing language about drug users" - }, - { - "text": "All these addicts need to be locked up", - "explanation": "Labeling: Uses the term 'addicts' as a dehumanizing label, Stereotyping: Implies all people with addiction are criminals, Separation: Creates a clear divide between 'these addicts' and others, Discrimination: Advocates for punitive measures rather than healthcare", - "description": "Advocating for punitive measures" - } - ] - - # Print model information - print(f"Using model: {model or 'default'} with client type: {client_type}") - - # First get style analysis for one of the texts to use in rewriting - print("\nGetting style analysis for rewriting...") - style_result = destigmatizer.analyze_text_llm( - test_cases[0]["text"], - client, - model=model - ) - print(f"Style analysis result: {style_result}") - style_instruct = str(style_result) - - # Test rewriting - print("\nTesting rewriting functionality...") - for case in test_cases: - print(f"\nOriginal text ({case['description']}): {case['text']}") - print(f"Explanation: {case['explanation']}") - - # Step 1: Remove labeling - rewrite_res = destigmatizer.rewrite_to_destigma( - case["text"], - case["explanation"], - style_instruct, - model=model, - client=client - ) - print(f"\nComparison:") - print(f"Original: {case['text']}") - print(f"Final: {rewrite_res}") - -if __name__ == "__main__": - # Set up argument parser - parser = setup_test_argument_parser('Test text rewriting functionality') - - # Parse arguments and get API key, model, and client type - api_key, model, client_type = parse_test_args(parser) - - # Run the test - test_rewriter(api_key=api_key, model=model, client_type=client_type) diff --git a/src/destigmatizer/tests/test_stigma_classifier.py b/src/destigmatizer/tests/test_stigma_classifier.py deleted file mode 100644 index 85bcc5f..0000000 --- a/src/destigmatizer/tests/test_stigma_classifier.py +++ /dev/null @@ -1,60 +0,0 @@ -import sys -import os -import json -import argparse -import destigmatizer - -from destigmatizer.tests.utils import setup_test_argument_parser, parse_test_args - -def test_stigma_classifier(api_key=None, model=None, client_type=None): - """ - Test the stigma classification functionality. - - Args: - api_key (str, optional): API key for the LLM service - model (str, optional): Model name to use for testing - client_type (str, optional): Type of client ("openai", "together", "claude", etc.) - """ - # Initialize client - try: - client = destigmatizer.initialize(api_key=api_key, client_type=client_type) - print("✓ Client initialization successful") - except Exception as e: - print(f"Error initializing client: {e}") - return - - # Test posts for classification - test_posts = { - "stigma_post_1": "All these junkies should be locked up, they're ruining our city", - "stigma_post_2": "Once an addict, always an addict. They'll never change.", - "non_stigma_post_1": "People struggling with addiction need support and understanding", - "non_stigma_post_2": "My friend is in recovery from opioid use disorder and is doing incredibly well." - } - - # Print model information - print(f"Using model: {model or 'default'} with client type: {client_type}") - - # Test stigma classification - print("\nTesting stigma classification...") - for post_type, post in test_posts.items(): - print(f"\nTesting on: {post}") - result = destigmatizer.classify_if_stigma( - post, - client=client, - model=model - ) - print(f"{post_type}: {result}") - - # If stigmatizing, show the explanation (which follows after the "S, " prefix) - if result.startswith("s,"): - print(f"Explanation: {result[2:].strip()}") - -if __name__ == "__main__": - # Set up argument parser - parser = setup_test_argument_parser('Test stigma classification functionality') - - # Parse arguments and get API key, model, and client type - api_key, model, client_type = parse_test_args(parser) - - # Run the test - test_stigma_classifier(api_key=api_key, model=model, client_type=client_type) diff --git a/src/destigmatizer/tests/test_text_analyzer.py b/src/destigmatizer/tests/test_text_analyzer.py deleted file mode 100644 index 846bf9e..0000000 --- a/src/destigmatizer/tests/test_text_analyzer.py +++ /dev/null @@ -1,56 +0,0 @@ -import sys -import os -import json -import argparse -import destigmatizer - -from destigmatizer.tests.utils import setup_test_argument_parser, parse_test_args - -def test_text_analyzer(api_key=None, model=None, client_type=None): - """ - Test the text style analysis functionality. - - Args: - api_key (str, optional): API key for the LLM service - model (str, optional): Model name to use for testing - client_type (str, optional): Type of client ("openai", "together", "claude", etc.) - """ - # Initialize client - try: - client = destigmatizer.initialize(api_key=api_key, client_type=client_type) - print("✓ Client initialization successful") - except Exception as e: - print(f"Error initializing client: {e}") - return - - # Test texts for analysis - test_texts = { - "simple_text": "This is a test sentence. It contains multiple parts.", - "complex_text": "The complexity of language analysis cannot be overstated; various factors contribute to the nuanced understanding of written communication. For instance, sentence length, vocabulary diversity, and punctuation usage all play crucial roles in determining text style.", - "mixed_text": "I hate this! Why can't people understand? It's not that complicated, is it? Sometimes I wonder if I'm the problem." - } - - # Print model information - print(f"Using model: {model or 'default'} with client type: {client_type}") - - # Test text analysis - print("\nTesting text analysis...") - for text_type, text in test_texts.items(): - print(f"\nAnalyzing: {text_type}") - print(f"Text: {text}") - result = destigmatizer.analyze_text_llm( - text, - client, - model=model - ) - print(f"Style analysis result: {result}") - -if __name__ == "__main__": - # Set up argument parser - parser = setup_test_argument_parser('Test text style analysis functionality') - - # Parse arguments and get API key, model, and client type - api_key, model, client_type = parse_test_args(parser) - - # Run the test - test_text_analyzer(api_key=api_key, model=model, client_type=client_type) diff --git a/src/destigmatizer/tests/test_workflow.py b/src/destigmatizer/tests/test_workflow.py deleted file mode 100644 index 1704992..0000000 --- a/src/destigmatizer/tests/test_workflow.py +++ /dev/null @@ -1,100 +0,0 @@ -import sys -import os -import json -import argparse -import destigmatizer - -from destigmatizer.tests.utils import setup_test_argument_parser, parse_test_args - -def test_workflow(api_key=None, model=None, client_type=None): - """ - Test the reframe workflow. - - Args: - api_key (str, optional): API key for the LLM service - model (str, optional): Model name to use for testing - client_type (str, optional): Type of client ("openai", "together" or "Claude") - """ - # Initialize client - try: - client = destigmatizer.initialize(api_key=api_key, client_type=client_type) - print("✓ Client initialization successful") - except Exception as e: - print(f"Error initializing client: {e}") - return - - # Print model information - print(f"Using model: {model or 'default'} with client type: {client_type}") - - # Test post - test_post = "junkies are causing problems in our neighborhood" - print(f"\nTesting workflow on post: \"{test_post}\"") - - # Test drug classification - print("\nTesting drug classification...") - drug_result = destigmatizer.classify_if_drug( - test_post, - client=client, - model=model - ) - print(f"Drug classification result: {drug_result}") - - # Step 1: Classify if stigma and get explanation - print("\nStep 1: Stigma classification and explanation...") - stigma_result = destigmatizer.classify_if_stigma( - test_post, - client=client, - model=model - ) - print(f"Stigma classification result: {stigma_result}") - - # Extract label and explanation from stigma classification result - if ', ' in stigma_result: - label, explanation = stigma_result.split(', ', 1) - else: - label = stigma_result - explanation = "" - - print(f"Extracted label: {label}") - print(f"Extracted explanation: {explanation}") - - # Step 2: Analyze text style - print("\nStep 2: Text style analysis...") - style_result = destigmatizer.analyze_text_llm( - test_post, - client, - model=model - ) - print(f"Style analysis result: {style_result}") - - # Step 3: Emotion detection - print("\nStep 3: Emotion detection...") - emotion = destigmatizer.get_emotion( - test_post, - client, - model=model - ) - print(f"Detected emotion: {emotion}") - - # Step 4: Rewriting with actual explanation and style - print("\nStep 4: Rewriting process...") - # First rewrite - rewrite_res = destigmatizer.rewrite_to_destigma( - test_post, - explanation, - str(style_result), - model=model, - client=client - ) - - print(f"Rewrite result: {rewrite_res}") - -if __name__ == "__main__": - # Set up argument parser - parser = setup_test_argument_parser('Test reframe workflow functionality') - - # Parse arguments and get API key, model, and client type - api_key, model, client_type = parse_test_args(parser) - - # Run the test - test_workflow(api_key=api_key, model=model, client_type=client_type) \ No newline at end of file diff --git a/src/destigmatizer/tests/utils.py b/src/destigmatizer/tests/utils.py deleted file mode 100644 index a0f6707..0000000 --- a/src/destigmatizer/tests/utils.py +++ /dev/null @@ -1,81 +0,0 @@ -"""Common utilities for tests.""" - -import os -import sys -import argparse -from typing import Optional, Tuple, Any - -from destigmatizer.utils import load_api_key, get_default_model, get_api_key_with_fallbacks - - -def get_api_key_for_testing(api_key: Optional[str] = None, client_type: str = "openai") -> str: - """ - Get API key for testing from parameter, environment variables, or secrets file. - - Args: - api_key: API key provided by parameter - client_type: Type of client ("openai", "together", or "claude") - - Returns: - str: API key - - Raises: - SystemExit: If no API key is found - """ - try: - api_key, _ = get_api_key_with_fallbacks(api_key, client_type) - return api_key - except ValueError as e: - print(f"Error: {e}") - print("Please provide an API key using --api_key or set the appropriate environment variable") - sys.exit(1) - - -def get_model_for_testing(model: Optional[str] = None, client_type: str = "openai") -> str: - """ - Get model name for testing, using default if not specified. - - Args: - model: Model name provided by parameter - client_type: Type of client ("openai", "together", or "claude") - - Returns: - str: Model name - """ - if model: - return model - return get_default_model(client_type) - - -def setup_test_argument_parser(description: str) -> argparse.ArgumentParser: - """ - Set up argument parser for test scripts. - - Args: - description: Description for the argument parser - - Returns: - argparse.ArgumentParser: Configured argument parser - """ - parser = argparse.ArgumentParser(description=description) - parser.add_argument('--api_key', help='API key for LLM service') - parser.add_argument('--model', help='Model name to use for testing') - parser.add_argument('--client_type', default='together', - help='Client type (e.g., openai, together, claude)') - return parser - - -def parse_test_args(parser: argparse.ArgumentParser) -> Tuple[str, str, str]: - """ - Parse common test arguments and get API key, model, and client type. - - Args: - parser: Configured argument parser - - Returns: - tuple: (api_key, model, client_type) - """ - args = parser.parse_args() - api_key = get_api_key_for_testing(args.api_key, args.client_type) - model = get_model_for_testing(args.model, args.client_type) - return api_key, model, args.client_type From 23ba511d33de03cdfe1a58b1d19797c4a674686a Mon Sep 17 00:00:00 2001 From: Muqi Guo <9076oliver@gmail.com> Date: Wed, 19 Mar 2025 16:01:53 -0400 Subject: [PATCH 3/3] Refactor configuration and utility scripts for destigmatizer; update example config and test text for improved clarity --- README.md | 34 -------------------- example.ipynb | 50 ++++++++++++++--------------- reframe_example_config.json | 2 +- src/destigmatizer/config_manager.py | 22 ++++++------- src/destigmatizer/test_pipe.py | 2 +- src/destigmatizer/utils.py | 20 ++++++------ 6 files changed, 48 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index d4e3187..c808428 100644 --- a/README.md +++ b/README.md @@ -53,37 +53,3 @@ print(f"Rewritten: {rewritten_text}") ``` - -Reframe includes integrated tests for all major functions. You can run tests in two ways: - -### Using the CLI command - -```bash -# Test all functions -reframe-test - -# Test a specific function -reframe-test drug --api_key YOUR_API_KEY - -# Use a specific model with Together AI -reframe-test --client_type together --model "meta-llama/Meta-Llama-3.1" - -# Use Claude -reframe-test --client_type claude --model "claude-3-haiku-20240307" - -# Using environment variables (recommended) -# First set the environment variables: -export OPENAI_API_KEY="your-openai-key" -export TOGETHER_API_KEY="your-together-key" -export ANTHROPIC_API_KEY="your-anthropic-key" - -# Then run tests without specifying the API key -reframe-test --client_type openai -reframe-test --client_type together -reframe-test --client_type claude -``` - -### Running test scripts directly -```bash -# From the root directory -python3 -m reframe.tests.run_all_tests diff --git a/example.ipynb b/example.ipynb index 9b3a18b..f579dec 100644 --- a/example.ipynb +++ b/example.ipynb @@ -4,9 +4,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Reframe Package Example Notebook\n", + "# Destigmatizer (Reframe) Package Example Notebook\n", "\n", - "This notebook demonstrates the core functionality of the Reframe package for analyzing and rewriting drug-related and stigmatizing content.\n", + "This notebook demonstrates the core functionality of the Destigmatizer package for analyzing and rewriting drug-related and stigmatizing content.\n", "\n", "## Features demonstrated:\n", "1. Classifying drug-related content\n", @@ -17,7 +17,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -43,14 +43,14 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Client initialized with model: gpt-4o-2024-11-20\n" + "Client initialized with model: gpt-4o\n" ] } ], @@ -73,13 +73,13 @@ "# Initialize client\n", "client = get_client('openai', api_key=api_key)\n", "# model = get_default_model('openai') # Get default model for this client type\n", - "model = \"gpt-4o-2024-11-20\"\n", + "model = \"gpt-4o\" \n", "print(f\"Client initialized with model: {model}\")" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 31, "metadata": {}, "outputs": [], "source": [ @@ -106,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 32, "metadata": {}, "outputs": [], "source": [ @@ -140,7 +140,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 33, "metadata": {}, "outputs": [ { @@ -201,7 +201,7 @@ " top_p: 0.9\n", " highly_creative:\n", " model_name: medium\n", - " temperature: 0.9\n", + " temperature: 0.0\n", " max_tokens: 2500\n", " top_p: 0.95\n", " frequency_penalty: 0.2\n", @@ -211,7 +211,7 @@ "\n", "Updated configuration:\n", "model_name: medium\n", - "temperature: 0.9\n", + "temperature: 0.0\n", "max_tokens: 2500\n", "top_p: 0.95\n", "frequency_penalty: 0.2\n" @@ -246,7 +246,7 @@ " config,\n", " name=\"highly_creative\",\n", " model_name=\"medium\",\n", - " temperature=0.9,\n", + " temperature=0.0,\n", " max_tokens=2500,\n", " top_p=0.95,\n", " frequency_penalty=0.2\n", @@ -271,7 +271,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 34, "metadata": {}, "outputs": [ { @@ -310,7 +310,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 35, "metadata": {}, "outputs": [ { @@ -356,7 +356,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 36, "metadata": {}, "outputs": [ { @@ -367,10 +367,10 @@ "Stigma classification result: ns\n", "\n", "Classifying stigmatizing language for: \"Addicts really need to get control of themselves. ...\"\n", - "Stigma classification result: s, labeling: uses the term 'addicts,' which is stigmatizing and reduces individuals to their substance use, stereotyping: assumes that people who use drugs lack willpower and oversimplifies addiction as a matter of self-control, separation: creates a divide by portraying people with addiction as fundamentally different or weaker, discrimination: implies that people with addiction are at fault for their condition, which can discourage empathy and support.\n", + "Stigma classification result: s, labeling: uses the term 'addicts' in a derogatory manner, stereotyping: assumes that people with addiction simply lack willpower, ignoring the complexity of substance use disorders, separation: implies a moral failing, creating a divide between those who use drugs and those who don't, discrimination: suggests that people with addiction are responsible for their condition and undeserving of empathy or support.\n", "\n", "Classifying stigmatizing language for: \"I think we should really work on the housing crisi...\"\n", - "Stigma classification result: s, labeling: refers to homeless individuals in a generalized and dehumanizing way, stereotyping: implies that homeless individuals are inherently \"scary,\" which perpetuates negative assumptions, separation: creates an \"us vs. them\" dynamic by portraying homeless individuals as a threat, discrimination: suggests fear-based attitudes that could justify exclusion or unfair treatment of homeless individuals, many of whom may struggle with substance use disorders.\n" + "Stigma classification result: s, labeling: refers to homeless individuals in a way that implies they are inherently frightening, stereotyping: implies that homeless individuals, who may include people with substance use disorders, are dangerous, separation: creates a divide between housed individuals and those experiencing homelessness, discrimination: suggests that homeless individuals are a problem to be managed rather than people in need of support and resources.\n" ] } ], @@ -402,7 +402,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 37, "metadata": {}, "outputs": [ { @@ -411,7 +411,7 @@ "text": [ "Analyzing text for drug content and stigma: \"Addicts really need to get control of themselves. ...\"\n", "Drug classification: d\n", - "Stigma classification: s, labeling: uses the term 'addicts,' which is stigmatizing and reduces individuals to their condition, stereotyping: assumes that people who use drugs lack willpower and oversimplifies addiction as a choice, separation: implies a moral or behavioral divide between people who use drugs and those who don't, discrimination: suggests that people with addiction are at fault for their condition, potentially justifying a lack of support or empathy.\n", + "Stigma classification: s, labeling: uses the term 'addicts' in a derogatory manner, stereotyping: assumes that people with addiction simply lack willpower, ignoring the complexity of substance use disorders, separation: implies a moral failing, creating a divide between those who use drugs and those who don't, discrimination: suggests that people with addiction are responsible for their condition and undeserving of empathy or support.\n", "\n", "Analyzing text for drug content and stigma: \"I really feel for people who suffer from substance...\"\n", "Drug classification: nd\n", @@ -419,7 +419,7 @@ "\n", "Analyzing text for drug content and stigma: \"I think we should really work on the housing crisi...\"\n", "Drug classification: nd\n", - "Stigma classification: s, labeling: refers to homeless individuals in a generalized and dehumanizing way, stereotyping: implies that homeless individuals are inherently \"scary,\" which perpetuates negative assumptions, separation: creates an \"us vs. them\" dynamic by portraying homeless individuals as a threat, discrimination: suggests fear-based attitudes that could lead to exclusion or unfair treatment of homeless individuals, many of whom may struggle with substance use disorders.\n" + "Stigma classification: s, stereotyping: implies that homeless individuals are inherently frightening, reinforcing negative generalizations, separation: creates a divide between housed individuals and those experiencing homelessness, suggesting they are 'other' and to be feared, discrimination: could lead to support for policies that further marginalize homeless individuals rather than addressing root causes.\n" ] } ], @@ -460,7 +460,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 38, "metadata": {}, "outputs": [ { @@ -496,7 +496,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 39, "metadata": {}, "outputs": [ { @@ -504,7 +504,7 @@ "output_type": "stream", "text": [ "Original: Addicts really need to get control of themselves. Just stop doing drugs. It seems like these people are just lacking willpower.\n", - "Rewritten: people experiencing substance use challenges need support and understanding. recovery is a complex process that requires compassion and resources.\n" + "Rewritten: individuals struggling with substance use need support and understanding. recovery is a complex process. it requires compassion and assistance.\n" ] } ], @@ -540,7 +540,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 40, "metadata": {}, "outputs": [ { @@ -548,7 +548,7 @@ "output_type": "stream", "text": [ "Original: Addicts really need to get control of themselves. Just stop doing drugs. It seems like these people are just lacking willpower.\n", - "Rewritten: people experiencing substance use challenges need support and understanding. recovery is a complex process that requires compassion and resources.\n" + "Rewritten: individuals struggling with substance use need support and understanding. recovery is a journey. it seems like these individuals face many challenges.\n" ] } ], diff --git a/reframe_example_config.json b/reframe_example_config.json index c31c987..48594d5 100644 --- a/reframe_example_config.json +++ b/reframe_example_config.json @@ -57,7 +57,7 @@ }, "highly_creative": { "model_name": "medium", - "temperature": 0.9, + "temperature": 0.0, "max_tokens": 2500, "top_p": 0.95, "frequency_penalty": 0.2 diff --git a/src/destigmatizer/config_manager.py b/src/destigmatizer/config_manager.py index 3b1c8c7..09a0e6e 100644 --- a/src/destigmatizer/config_manager.py +++ b/src/destigmatizer/config_manager.py @@ -1,5 +1,5 @@ """ -Utility script for managing reframe configurations. +Utility script for managing destigmatizer configurations. """ import os @@ -238,7 +238,7 @@ def get_user_config_path() -> str: str: Path to the user configuration file """ home_dir = os.path.expanduser("~") - config_dir = os.path.join(home_dir, ".reframe") + config_dir = os.path.join(home_dir, ".destigmatizer") return os.path.join(config_dir, "config.json") def get_effective_config() -> Dict[str, Any]: @@ -270,12 +270,12 @@ def get_effective_config() -> Dict[str, Any]: package_dir = os.path.dirname(module_dir) locations = [ - os.path.join(module_dir, "config", "reframe_config.json"), - os.path.join(package_dir, "config", "reframe_config.json"), - os.path.join(os.getcwd(), "reframe_config.json"), - os.path.join(os.getcwd(), "config", "reframe_config.json"), - os.path.join(os.path.expanduser("~"), ".reframe", "config.json"), - os.environ.get("REFRAME_CONFIG_PATH") + os.path.join(module_dir, "config", "destigmatizer_config.json"), + os.path.join(package_dir, "config", "destigmatizer_config.json"), + os.path.join(os.getcwd(), "destigmatizer_config.json"), + os.path.join(os.getcwd(), "config", "destigmatizer_config.json"), + os.path.join(os.path.expanduser("~"), ".destigmatizer", "config.json"), + os.environ.get("DESTIGMATIZER_CONFIG_PATH") ] for location in locations: @@ -306,12 +306,12 @@ def display_config(config: Dict[str, Any], indent: int = 0) -> None: print(f"{indent_str}{key}: {value}") def main(): - parser = argparse.ArgumentParser(description="Reframe Configuration Manager") + parser = argparse.ArgumentParser(description="Destigmatizer Configuration Manager") subparsers = parser.add_subparsers(dest="command", help="Command to execute") # Create default config command create_parser = subparsers.add_parser("create", help="Create default configuration file") - create_parser.add_argument("--output", "-o", default="./reframe_config.json", + create_parser.add_argument("--output", "-o", default="./destigmatizer_config.json", help="Output path for the configuration file") create_parser.add_argument("--overwrite", action="store_true", help="Overwrite existing configuration file") @@ -396,7 +396,7 @@ def main(): print("Current effective configuration:") display_config(config) else: - print("No configuration found. Run 'reframe config init' to create a default configuration.") + print("No configuration found. Run 'destigmatizer config init' to create a default configuration.") else: parser.print_help() diff --git a/src/destigmatizer/test_pipe.py b/src/destigmatizer/test_pipe.py index 8c3eea9..a34470d 100644 --- a/src/destigmatizer/test_pipe.py +++ b/src/destigmatizer/test_pipe.py @@ -18,7 +18,7 @@ pipeline.add_filter(rewrite_text_filter) # Process text -text = "Addicts really need to get control of themselves." +text = "I really feel for people who suffer from substance use disorder, being unable to control an impulse due to dependency sounds scary." result = pipeline.process(text) print(f"Original: {text}") diff --git a/src/destigmatizer/utils.py b/src/destigmatizer/utils.py index 5b6f139..9a2cc94 100644 --- a/src/destigmatizer/utils.py +++ b/src/destigmatizer/utils.py @@ -1,4 +1,4 @@ -"""Utility functions for the reframe package.""" +"""Utility functions for the destigmatizer package.""" import os import json @@ -265,9 +265,9 @@ def load_user_model_configs() -> Dict[str, Any]: Load user configuration from standard locations. Checks multiple locations in this order: - 1. Environment variable REFRAME_CONFIG_PATH - 2. User's home directory ~/.reframe/config.json - 3. Current working directory reframe_config.json or config/reframe_config.json + 1. Environment variable DESTIGMATIZER_CONFIG_PATH + 2. User's home directory ~/.destigmatizer/config.json + 3. Current working directory destigmatizer_config.json or config/destigmatizer_config.json 4. Package directory Returns: @@ -275,10 +275,10 @@ def load_user_model_configs() -> Dict[str, Any]: """ # Standard locations to check locations = [ - os.environ.get("REFRAME_CONFIG_PATH"), - os.path.join(os.path.expanduser("~"), ".reframe", "config.json"), - os.path.join(os.getcwd(), "reframe_config.json"), - os.path.join(os.getcwd(), "config", "reframe_config.json"), + os.environ.get("DESTIGMATIZER_CONFIG_PATH"), + os.path.join(os.path.expanduser("~"), ".destigmatizer", "config.json"), + os.path.join(os.getcwd(), "destigmatizer_config.json"), + os.path.join(os.getcwd(), "config", "destigmatizer_config.json"), ] # Also check in package directory @@ -287,8 +287,8 @@ def load_user_model_configs() -> Dict[str, Any]: package_dir = os.path.dirname(module_dir) locations.extend([ - os.path.join(module_dir, "config", "reframe_config.json"), - os.path.join(package_dir, "config", "reframe_config.json"), + os.path.join(module_dir, "config", "destigmatizer_config.json"), + os.path.join(package_dir, "config", "destigmatizer_config.json"), ]) except Exception: pass