├── .gitignore ├── .coverage ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .idea ├── FlashLearn.iml ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── vcs.xml └── workspace.xml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── dist ├── flashlearn-1.0.0-py3-none-any.whl ├── flashlearn-1.0.0.tar.gz ├── flashlearn-1.0.2-py3-none-any.whl ├── flashlearn-1.0.2.tar.gz ├── flashlearn-1.0.3-py3-none-any.whl ├── flashlearn-1.0.3.tar.gz ├── flashlearn-1.0.4-py3-none-any.whl └── flashlearn-1.0.4.tar.gz ├── examples ├── Agent Patterns │ ├── README.md │ ├── agents.py │ ├── classification_only.py │ ├── definitions │ │ ├── classification.json │ │ ├── emotional_statements.json │ │ ├── emotional_summary.json │ │ ├── explanation.json │ │ └── learn_skills.py │ ├── summary_statements_explanation_then_classification.py │ ├── summary_statements_then_classification.py │ └── summary_then_classification.py ├── Code only examples │ ├── anthropic_contextual_retrival_for_rag.md │ ├── batch_api.py │ ├── deepseek_inifinite_context.py │ ├── discover_and_classify_clusters.py │ ├── discover_themes_in_product_reviews.py │ ├── image_classification.py │ ├── learn_new_skill.py │ ├── learn_new_skill_img.py │ ├── load_skill.py │ ├── load_skill_img.py │ ├── perplexity_clone.py │ └── sentiment_classification.py ├── Customer service │ └── classify_tickets.md ├── Finance │ └── parse_financial_report_data.md ├── Marketing │ └── customer_segmentation.md ├── Personal asistant │ └── research_assistant.md ├── Product intelligence │ ├── discover_trends_in_prodcut _reviews.md │ └── user_behaviour_analysis.md ├── README.md ├── Sales │ ├── personalized_emails.md │ └── sentiment_classification.md ├── Software development │ └── automated_pr_reviews.md ├── browser_use_price_matching │ ├── README.md │ ├── learn_skill.py │ ├── learn_skill_select_best_product.py │ ├── make_query.json │ ├── product_price_matching.py │ └── select_product.json └── tests │ ├── DiscoverLabelsSkill.json │ ├── integration_test.py │ └── tasks.jsonl ├── flashlearn.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt ├── requires.txt └── top_level.txt ├── flashlearn ├── __init__.py ├── core │ ├── __init__.py │ ├── flash_client.py │ ├── orchestration.py │ └── tests │ │ ├── test_flash_client.py │ │ └── test_orchestration.py ├── skills │ ├── README.md │ ├── __init__.py │ ├── __pycache__ │ │ └── learn_skill.cpython-312.pyc │ ├── base_data_skill.py │ ├── base_skill.py │ ├── classification.py │ ├── collect_all_code.py │ ├── discover_labels.py │ ├── general_skill.py │ ├── learn_skill.py │ ├── tests │ │ ├── .hypothesis │ │ │ ├── examples │ │ │ │ └── 099e569465a026b4 │ │ │ │ │ └── bec021b4f368e306 │ │ │ └── unicode_data │ │ │ │ └── 15.0.0 │ │ │ │ ├── charmap.json.gz │ │ │ │ └── codec-utf-8.json.gz │ │ ├── test_base_data_skill.py │ │ ├── test_base_skill.py │ │ ├── test_classification.py │ │ ├── test_discover_labels.py │ │ ├── test_general_skill.py │ │ └── test_learn_skill.py │ └── toolkit │ │ ├── README.md │ │ ├── __init__.py │ │ ├── generate_init.py │ │ ├── generate_toolkit.py │ │ ├── simple_search.py │ │ └── validate_json_files.py └── utils │ ├── __init__.py │ ├── demo_data.py │ ├── logging_utils.py │ ├── tests │ ├── test_demo_data.py │ └── test_token_utils.py │ └── token_utils.py ├── pyproject.toml └── requirements.txt / .gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__/ 3 | ../.venv/ 4 | ../build/ 5 | flashlearn/dist/ 6 | *.egg-info/ 7 | ../dist/ 8 | ../todo.txt 9 | ../flashlearn.egg-info/ 10 | /flashlearn.egg-info/ 11 | /dist/ 12 | -------------------------------------------------------------------------------- /.coverage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/.coverage -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__/ 3 | ../.venv/ 4 | ../build/ 5 | flashlearn/dist/ 6 | *.egg-info/ 7 | ../dist/ 8 | ../todo.txt 9 | ../flashlearn.egg-info/ 10 | /flashlearn.egg-info/ 11 | /dist/ 12 | *.pyc 13 | __pycache__/ 14 | ../.venv/ 15 | ../build/ 16 | flashlearn/dist/ 17 | *.egg-info/ 18 | ../dist/ 19 | ../todo.txt 20 | ../flashlearn.egg-info/ 21 | /flashlearn.egg-info/ 22 | /dist/ 23 | -------------------------------------------------------------------------------- /.idea/FlashLearn.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | info@clerkly.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing & Community 2 | 3 | - Licensed under MIT. 4 | - [Fork us](flashlearn/) to add new skills, fix bugs, or create new examples. 5 | - We aim to make robust LLM workflows accessible to all startups. 6 | - All code needs at least **95%** test coverage 7 | - Explore the [examples folder](examples/) for more advanced usage patterns. 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2025 Giaco in GIaco d.o.o 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We currently support version 1.0.x. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 1.0.x | :white_check_mark: | 10 | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Report it to info@clerkly.co 15 | -------------------------------------------------------------------------------- /dist/flashlearn-1.0.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/dist/flashlearn-1.0.0-py3-none-any.whl -------------------------------------------------------------------------------- /dist/flashlearn-1.0.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/dist/flashlearn-1.0.0.tar.gz -------------------------------------------------------------------------------- /dist/flashlearn-1.0.2-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/dist/flashlearn-1.0.2-py3-none-any.whl -------------------------------------------------------------------------------- /dist/flashlearn-1.0.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/dist/flashlearn-1.0.2.tar.gz -------------------------------------------------------------------------------- /dist/flashlearn-1.0.3-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/dist/flashlearn-1.0.3-py3-none-any.whl -------------------------------------------------------------------------------- /dist/flashlearn-1.0.3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/dist/flashlearn-1.0.3.tar.gz -------------------------------------------------------------------------------- /dist/flashlearn-1.0.4-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/dist/flashlearn-1.0.4-py3-none-any.whl -------------------------------------------------------------------------------- /dist/flashlearn-1.0.4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/dist/flashlearn-1.0.4.tar.gz -------------------------------------------------------------------------------- /examples/Agent Patterns/README.md: -------------------------------------------------------------------------------- 1 | # Is More Agents Better? 2 | 3 | A demonstration of single-agent vs. multi-agent text classification pipelines on IMDB movie reviews. 4 | 5 | ## Overview 6 | 7 | This project explores how adding more agents (or steps) to a classification pipeline affects accuracy. We train a basic sentiment classifier on IMDB data, then progressively insert additional “agents” to provide emotional summaries, emotional statements, and explanations. Finally, we compare how each step affects the final classification accuracy (i.e., determining whether a review is positive or negative). 8 | 9 | The key takeaway? More agents does not necessarily mean better accuracy! 10 | 11 | ## Project Structure 12 | 13 | 1. **Skill Definitions** 14 | - Located in the `definitions/` folder. 15 | - Each JSON file (e.g., `classification.json`, `emotional_summary.json`, etc.) defines a “skill” learned by the flashlearn library (e.g., how to classify sentiment, summarize emotions, or extract statements). 16 | 17 | 2. **Pipelines** 18 | - We implement four progressively more complex pipelines in standalone scripts: 19 | 1. `classification_only.py` 20 | - No intermediate steps — the classifier alone decides the sentiment. 21 | 2. `summary_then_classification.py` 22 | - Runs the “emotional_summary” agent, then feeds that summary into the classifier. 23 | 3. `summary_statements_then_classification.py` 24 | - Executes “emotional_summary” → “emotional_statements” → classification. 25 | 4. `summary_statements_explanation_then_classification.py` 26 | - The most elaborate pipeline: “emotional_summary” → “emotional_statements” → “explanation” → classification. 27 | 28 | 3. **IMDB Dataset** 29 | - We rely on the flashlearn utility `imdb_reviews_50k` to read a subset of the IMDB movie reviews dataset. Each record includes: 30 | - `text`: The movie review text. 31 | - `sentiment`: The true label (`"positive"` or `"negative"`). 32 | 33 | 4. **Accuracy Comparison** 34 | - Each pipeline removes the `sentiment` field before classification. After predictions, we compare them to the hidden ground truth. 35 | 36 | ## Results 37 | 38 | Below is a table summarizing the final accuracy of each pipeline on a full sample: 39 | 40 | | Pipeline Approach | Accuracy | 41 | |-------------------------------------------------------------|----------| 42 | | Step 1: Classification-Only | 0.95 | 43 | | Step 2: Summary → Classification | 0.94 | 44 | | Step 3: Summary → Statements → Classification | 0.93 | 45 | | Step 4: Summary → Statements → Explanation → Classification | 0.94 | 46 | 47 | **Key Insight**: While more intermediate agents can offer additional analysis, context, or interpretability, they don’t necessarily improve raw sentiment accuracy. In fact, the single-agent approach (Step 1) achieved the highest accuracy. 48 | 49 | ## Usage 50 | 51 | 1. **Install Requirements** 52 | - Ensure you have Python 3.8+ and the [flashlearn](https://github.com/flashlearn-xyz/flashlearn) library (or similar) installed. 53 | - Example: 54 | ```bash 55 | pip install flashlearn 56 | ``` 57 | 58 | 2. **Prepare Skill Definitions** 59 | - Make sure `classification.json`, `emotional_summary.json`, `emotional_statements.json`, and `explanation.json` are in the `definitions/` folder. 60 | 61 | 3. **Run Pipelines** 62 | - Classification only: 63 | ```bash 64 | python classification_only.py 65 | ``` 66 | - Summary → Classification: 67 | ```bash 68 | python summary_then_classification.py 69 | ``` 70 | - Summary → Statements → Classification: 71 | ```bash 72 | python summary_statements_then_classification.py 73 | ``` 74 | - Summary → Statements → Explanation → Classification: 75 | ```bash 76 | python summary_statements_explanation_then_classification.py 77 | ``` 78 | 79 | 4. **Observe Accuracy** 80 | - Each script prints out predicted labels alongside the actual (hidden) sentiment and computes a final accuracy percentage. 81 | 82 | ## Project Takeaways 83 | 84 | 1. **Simplicity Can Win** 85 | - The single-step pipeline performed best (95% accuracy). 86 | - Multi-step approaches can sometimes introduce abstraction or partial misinterpretation of text. 87 | 88 | 2. **Modular Insights** 89 | - Adding “emotional_summary,” “emotional_statements,” or “explanation” agents can be valuable if you need deeper insights, interpretability, or advanced data constructs. 90 | 91 | 3. **Context Matters** 92 | - Different datasets or tasks might benefit more from extra agents—no one-size-fits-all solution. 93 | 94 | ## Contributing 95 | 96 | - Feel free to open PRs or issues if you discover improvements or new ways to chain multiple agents while preserving or boosting accuracy. 97 | 98 | ## License 99 | 100 | This project is provided “as is,” with no warranty. Refer to the accompanying license file (if provided) or use it under the terms of your choice. 101 | 102 | --- 103 | 104 | **Is More Agents Better?** 105 | In short, not always. While multi-agent pipelines offer richer insights, maximum classification accuracy is sometimes easier to achieve with a single, carefully trained agent! -------------------------------------------------------------------------------- /examples/Agent Patterns/agents.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/examples/Agent Patterns/agents.py -------------------------------------------------------------------------------- /examples/Agent Patterns/classification_only.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | from flashlearn.skills import GeneralSkill 5 | from flashlearn.utils import imdb_reviews_50k 6 | 7 | def main(): 8 | # 1. Load classification skill 9 | with open("definitions/classification.json", "r", encoding="utf-8") as f: 10 | classification_def = json.load(f) 11 | classification_skill = GeneralSkill.load_skill(classification_def) 12 | 13 | # 2. Load IMDB data (remove sentiment for classification) 14 | data = imdb_reviews_50k(full=True)[:1000] 15 | sentiments = [] 16 | for row in data: 17 | sentiments.append(row["sentiment"]) 18 | del row["sentiment"] 19 | 20 | # 3. Run classification 21 | tasks = classification_skill.create_tasks(data) 22 | results = classification_skill.run_tasks_in_parallel(tasks, max_requests_per_minute=500, request_timeout=20) 23 | 24 | # 4. Compare predictions to actual 25 | correct = 0 26 | for task_id_str, output in results.items(): 27 | tid = int(task_id_str) 28 | predicted = output.get("label", "unknown") 29 | actual = sentiments[tid] 30 | if predicted == actual: 31 | correct += 1 32 | #print(f"Review {tid} => Predicted: {predicted}, Actual: {actual}") 33 | 34 | accuracy = correct / len(data) if data else 0 35 | print(f"[Step 1] Classification-Only Accuracy: {accuracy:.2f}") 36 | 37 | if __name__ == "__main__": 38 | main() -------------------------------------------------------------------------------- /examples/Agent Patterns/definitions/classification.json: -------------------------------------------------------------------------------- 1 | { 2 | "skill_class": "GeneralSkill", 3 | "system_prompt": "Exactly populate the provided function definition", 4 | "function_definition": { 5 | "type": "function", 6 | "function": { 7 | "name": "SentimentClassifier", 8 | "description": "Classifies a given review into positive or negative sentiment.", 9 | "strict": true, 10 | "parameters": { 11 | "type": "object", 12 | "properties": { 13 | "label": { 14 | "type": "string", 15 | "enum": [ 16 | "positive", 17 | "negative" 18 | ] 19 | } 20 | }, 21 | "required": [ 22 | "label" 23 | ], 24 | "additionalProperties": false 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /examples/Agent Patterns/definitions/emotional_statements.json: -------------------------------------------------------------------------------- 1 | { 2 | "skill_class": "GeneralSkill", 3 | "system_prompt": "Exactly populate the provided function definition", 4 | "function_definition": { 5 | "type": "function", 6 | "function": { 7 | "name": "ExtractEmotionalStatements", 8 | "description": "Extract key emotional statements from given text reviews, returning the results in a structured JSON format.", 9 | "strict": true, 10 | "parameters": { 11 | "type": "object", 12 | "properties": { 13 | "emotional_statements": { 14 | "type": "array", 15 | "items": { 16 | "type": "string" 17 | } 18 | } 19 | }, 20 | "required": [ 21 | "emotional_statements" 22 | ], 23 | "additionalProperties": false 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /examples/Agent Patterns/definitions/emotional_summary.json: -------------------------------------------------------------------------------- 1 | { 2 | "skill_class": "GeneralSkill", 3 | "system_prompt": "Exactly populate the provided function definition", 4 | "function_definition": { 5 | "type": "function", 6 | "function": { 7 | "name": "SummarizeEmotions", 8 | "description": "This function summarizes the emotions expressed in a series of reviews", 9 | "strict": true, 10 | "parameters": { 11 | "type": "object", 12 | "properties": { 13 | "emotional_summary": { 14 | "type": "string" 15 | } 16 | }, 17 | "required": [ 18 | "emotional_summary" 19 | ], 20 | "additionalProperties": false 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /examples/Agent Patterns/definitions/explanation.json: -------------------------------------------------------------------------------- 1 | { 2 | "skill_class": "GeneralSkill", 3 | "system_prompt": "Exactly populate the provided function definition", 4 | "function_definition": { 5 | "type": "function", 6 | "function": { 7 | "name": "CategorizeReviews", 8 | "description": "Categorizes movie reviews as positive or negative and provides an explanation for the sentiment determination.", 9 | "strict": true, 10 | "parameters": { 11 | "type": "object", 12 | "properties": { 13 | "sentiment": { 14 | "type": "string", 15 | "enum": [ 16 | "positive", 17 | "negative" 18 | ] 19 | }, 20 | "explanation": { 21 | "type": "string" 22 | } 23 | }, 24 | "required": [ 25 | "sentiment", 26 | "explanation" 27 | ], 28 | "additionalProperties": false 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /examples/Agent Patterns/definitions/learn_skills.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | import dotenv 5 | from flashlearn.skills.learn_skill import LearnSkill 6 | from flashlearn.utils import imdb_reviews_50k 7 | dotenv.load_dotenv() 8 | def main(): 9 | # (Optional) Provide your API key, if required 10 | # os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY" 11 | 12 | # -------------------------------------------------------------------------- 13 | # STEP 1: Load IMDB data and REMOVE the existing sentiment label from each record 14 | # -------------------------------------------------------------------------- 15 | data = imdb_reviews_50k(sample=20) # small sample for demonstration 16 | # Save original sentiment in a separate field, then remove it 17 | 18 | # NOTE: We'll use this same data to learn all needed skills. 19 | # Usually you’d separate training and test sets, but for simplicity we 20 | # reuse the same “data” to define tasks (the library is demonstration-based). 21 | 22 | # -------------------------------------------------------------------------- 23 | # STEP 2: Define a single agent skill (no reflection/multi-step) 24 | # -------------------------------------------------------------------------- 25 | single_learner = LearnSkill(model_name="gpt-4o-mini", verbose=True) 26 | 27 | single_task_prompt = ( 28 | "You are a basic sentiment classifier. Return JSON with key 'label' " 29 | "which is strictly 'positive' or 'negative'." 30 | ) 31 | 32 | single_skill = single_learner.learn_skill( 33 | data, 34 | task=single_task_prompt, 35 | model_name="gpt-4o-mini" 36 | ) 37 | 38 | single_skill_path = "classification.json" 39 | single_skill.save(single_skill_path) 40 | print(f"Saved Single-Agent classification.json") 41 | 42 | # -------------------------------------------------------------------------- 43 | # STEP 3: Define Reflection-based approach with TWO agents (two separate skills) 44 | # a) Agent A: “First guess” 45 | # b) Agent B: “If there's doubt, revise classification” 46 | # -------------------------------------------------------------------------- 47 | reflection_learner = LearnSkill(model_name="gpt-4o-mini", verbose=True) 48 | reflection_prompt_A = ( 49 | "You are a sentiment summarizer and you summarize emotions in the text" 50 | "Output JSON with a key 'emotional_summary' set" 51 | ) 52 | skill_reflection_A = reflection_learner.learn_skill( 53 | data, task=reflection_prompt_A, model_name="gpt-4o-mini" 54 | ) 55 | skill_reflection_A.save("emotional_summary.json") 56 | print("Saved Reflection emotional_summary.json") 57 | 58 | reflection_prompt_B = ( 59 | "YOu make extract key emotional statements from the text" 60 | "Finally, return JSON with key 'emotional_statements'" 61 | "Where you list all emotional statements." 62 | ) 63 | # We can reuse the same reflection_learner or a new one 64 | skill_reflection_B = reflection_learner.learn_skill( 65 | data, task=reflection_prompt_B, model_name="gpt-4o-mini" 66 | ) 67 | skill_reflection_B.save("emotional_statements.json") 68 | print("Saved emotional_statements skill") 69 | 70 | # -------------------------------------------------------------------------- 71 | # STEP 4: Multi-Agent Approach that BUILDS on reflection but adds an extra step 72 | # e.g., A third agent or aggregator that merges final decisions or appends an explanation 73 | # -------------------------------------------------------------------------- 74 | multi_learner = LearnSkill(model_name="gpt-4o-mini", verbose=True) 75 | 76 | # Example aggregator: merges A & B or clarifies the final sentiment 77 | # In a real pipeline, you might pass Agent A + B outputs to this aggregator. 78 | multi_prompt = ( 79 | "You provide explanation why you think this review is positive or negative on key explanation." 80 | ) 81 | skill_multi_agent = multi_learner.learn_skill( 82 | data, task=multi_prompt, model_name="gpt-4o-mini" 83 | ) 84 | skill_multi_agent.save("explanation.json") 85 | print("Saved explanation.json") 86 | 87 | print("All skills learned and saved!") 88 | 89 | if __name__ == "__main__": 90 | main() -------------------------------------------------------------------------------- /examples/Agent Patterns/summary_statements_explanation_then_classification.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import json 4 | 5 | from flashlearn.skills import GeneralSkill 6 | from flashlearn.utils import imdb_reviews_50k 7 | 8 | def main(): 9 | # 1. Load all required skills 10 | with open("definitions/emotional_summary.json", "r", encoding="utf-8") as f: 11 | summary_def = json.load(f) 12 | summary_skill = GeneralSkill.load_skill(summary_def) 13 | 14 | with open("definitions/emotional_statements.json", "r", encoding="utf-8") as f: 15 | statements_def = json.load(f) 16 | statements_skill = GeneralSkill.load_skill(statements_def) 17 | 18 | with open("definitions/explanation.json", "r", encoding="utf-8") as f: 19 | explanation_def = json.load(f) 20 | explanation_skill = GeneralSkill.load_skill(explanation_def) 21 | 22 | with open("definitions/classification.json", "r", encoding="utf-8") as f: 23 | classification_def = json.load(f) 24 | classification_skill = GeneralSkill.load_skill(classification_def) 25 | 26 | # 2. Load data, remove sentiments 27 | data = imdb_reviews_50k(full=True)[:1000] 28 | sentiments = [] 29 | for row in data: 30 | sentiments.append(row["sentiment"]) 31 | del row["sentiment"] 32 | 33 | # 3. Summaries 34 | tasks_summary = summary_skill.create_tasks(data) 35 | results_summary = summary_skill.run_tasks_in_parallel(tasks_summary, request_timeout=120) 36 | for tid_str, out_sum in results_summary.items(): 37 | tid = int(tid_str) 38 | data[tid]["emotional_summary"] = out_sum.get("emotional_summary", "") 39 | 40 | # 4. Statements 41 | tasks_statements = statements_skill.create_tasks(data) 42 | results_statements = statements_skill.run_tasks_in_parallel(tasks_statements, request_timeout=120) 43 | for tid_str, out_stat in results_statements.items(): 44 | tid = int(tid_str) 45 | data[tid]["emotional_statements"] = out_stat.get("emotional_statements", []) 46 | 47 | # 5. Explanation 48 | tasks_explanation = explanation_skill.create_tasks(data) 49 | results_explanation = explanation_skill.run_tasks_in_parallel(tasks_explanation, request_timeout=120) 50 | for tid_str, out_exp in results_explanation.items(): 51 | tid = int(tid_str) 52 | data[tid]["explanation"] = out_exp.get("explanation", "") 53 | 54 | # 6. Classification 55 | tasks_class = classification_skill.create_tasks(data) 56 | results_class = classification_skill.run_tasks_in_parallel(tasks_class, request_timeout=120) 57 | 58 | # 7. Compare predictions 59 | correct = 0 60 | for task_id_str, out_cls in results_class.items(): 61 | tid = int(task_id_str) 62 | predicted = out_cls.get("label", "unknown") 63 | actual = sentiments[tid] 64 | if predicted == actual: 65 | correct += 1 66 | 67 | #print(f"Review {tid}") 68 | #print(f" Summary: {data[tid]['emotional_summary']}") 69 | #print(f" Statements: {data[tid]['emotional_statements']}") 70 | #print(f" Explanation: {data[tid]['explanation']}") 71 | #print(f" -> Predicted: {predicted}, Actual: {actual}\n") 72 | 73 | accuracy = correct / len(data) if data else 0 74 | print(f"[Step 4] Summary → Statements → Explanation → Classification Accuracy: {accuracy:.2f}") 75 | 76 | if __name__ == "__main__": 77 | main() -------------------------------------------------------------------------------- /examples/Agent Patterns/summary_statements_then_classification.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | from flashlearn.skills import GeneralSkill 5 | from flashlearn.utils import imdb_reviews_50k 6 | 7 | def main(): 8 | # 1. Load needed skills 9 | with open("definitions/emotional_summary.json", "r", encoding="utf-8") as f: 10 | summary_def = json.load(f) 11 | summary_skill = GeneralSkill.load_skill(summary_def) 12 | 13 | with open("definitions/emotional_statements.json", "r", encoding="utf-8") as f: 14 | statements_def = json.load(f) 15 | statements_skill = GeneralSkill.load_skill(statements_def) 16 | 17 | with open("definitions/classification.json", "r", encoding="utf-8") as f: 18 | classification_def = json.load(f) 19 | classification_skill = GeneralSkill.load_skill(classification_def) 20 | 21 | # 2. Load data, remove sentiments 22 | data = imdb_reviews_50k(full=True)[:1000] 23 | sentiments = [] 24 | for row in data: 25 | sentiments.append(row["sentiment"]) 26 | del row["sentiment"] 27 | 28 | # 3. Run “emotional_summary” 29 | tasks_summary = summary_skill.create_tasks(data) 30 | results_summary = summary_skill.run_tasks_in_parallel(tasks_summary, request_timeout=120) 31 | for task_id_str, out_sum in results_summary.items(): 32 | tid = int(task_id_str) 33 | data[tid]["emotional_summary"] = out_sum.get("emotional_summary", "") 34 | 35 | # 4. Run “emotional_statements” 36 | tasks_statements = statements_skill.create_tasks(data) 37 | results_statements = statements_skill.run_tasks_in_parallel(tasks_statements, request_timeout=120) 38 | for task_id_str, out_stat in results_statements.items(): 39 | tid = int(task_id_str) 40 | data[tid]["emotional_statements"] = out_stat.get("emotional_statements", []) 41 | 42 | # 5. Finally, run classification 43 | tasks_class = classification_skill.create_tasks(data) 44 | results_class = classification_skill.run_tasks_in_parallel(tasks_class, request_timeout=120) 45 | 46 | # 6. Compare predictions 47 | correct = 0 48 | for task_id_str, out_cls in results_class.items(): 49 | tid = int(task_id_str) 50 | predicted = out_cls.get("label", "unknown") 51 | actual = sentiments[tid] 52 | if predicted == actual: 53 | correct += 1 54 | #print(f"Review {tid}") 55 | #print(f" Summary: {data[tid]['emotional_summary']}") 56 | #print(f" Statements: {data[tid]['emotional_statements']}") 57 | #print(f" -> Predicted: {predicted}, Actual: {actual}\n") 58 | 59 | accuracy = correct / len(data) if data else 0 60 | print(f"[Step 3] Summary → Statements → Classification Accuracy: {accuracy:.2f}") 61 | 62 | if __name__ == "__main__": 63 | main() -------------------------------------------------------------------------------- /examples/Agent Patterns/summary_then_classification.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | from flashlearn.skills import GeneralSkill 5 | from flashlearn.utils import imdb_reviews_50k 6 | 7 | def main(): 8 | # 1. Load both “emotional_summary” and “classification” skills 9 | with open("definitions/emotional_summary.json", "r", encoding="utf-8") as f: 10 | summary_def = json.load(f) 11 | summary_skill = GeneralSkill.load_skill(summary_def) 12 | 13 | with open("definitions/classification.json", "r", encoding="utf-8") as f: 14 | classification_def = json.load(f) 15 | classification_skill = GeneralSkill.load_skill(classification_def) 16 | 17 | # 2. Load data, remove sentiments 18 | data = imdb_reviews_50k(full=True)[:1000] 19 | sentiments = [] 20 | for row in data: 21 | sentiments.append(row["sentiment"]) 22 | del row["sentiment"] 23 | 24 | # 3. Run "emotional_summary" skill 25 | tasks_summary = summary_skill.create_tasks(data) 26 | results_summary = summary_skill.run_tasks_in_parallel(tasks_summary, request_timeout=120) 27 | 28 | # 4. Attach summarized info back into the same data 29 | for task_id_str, out_sum in results_summary.items(): 30 | tid = int(task_id_str) 31 | # Our skill should output a key “emotional_summary” 32 | data[tid]["emotional_summary"] = out_sum.get("emotional_summary", "") 33 | 34 | # 5. Now run classification 35 | tasks_class = classification_skill.create_tasks(data) 36 | results_class = classification_skill.run_tasks_in_parallel(tasks_class, request_timeout=120) 37 | 38 | # 6. Compare predictions 39 | correct = 0 40 | for task_id_str, out_cls in results_class.items(): 41 | tid = int(task_id_str) 42 | predicted = out_cls.get("label", "unknown") 43 | actual = sentiments[tid] 44 | if predicted == actual: 45 | correct += 1 46 | #print(f"Review {tid} => Summary: {data[tid]['emotional_summary']}") 47 | #print(f" -> Predicted: {predicted}, Actual: {actual}\n") 48 | 49 | accuracy = correct / len(data) if data else 0 50 | print(f"[Step 2] Summary → Classification Accuracy: {accuracy:.2f}") 51 | 52 | if __name__ == "__main__": 53 | main() -------------------------------------------------------------------------------- /examples/Code only examples/batch_api.py: -------------------------------------------------------------------------------- 1 | from openai import OpenAI 2 | import json 3 | from flashlearn.skills.classification import ClassificationSkill 4 | from flashlearn.utils import demo_data, imdb_reviews_50k 5 | 6 | 7 | def main(): 8 | # Step 1: Provide your OpenAI 9 | # os.environ["OPENAI_API_KEY"] = 'YOUR API KEY' 10 | 11 | client = OpenAI() 12 | 13 | # Step 2: Load sample data (list of dicts with "review" and "sentiment" keys) 14 | reviews = imdb_reviews_50k(sample=100) 15 | 16 | # Step 3: Initialize the Classification Skill 17 | skill = ClassificationSkill( 18 | model_name="gpt-4o-mini", 19 | categories=["positive", "negative"], 20 | max_categories=1, 21 | system_prompt="We want to classify short movie reviews by sentiment." 22 | ) 23 | 24 | # Step 4: Prepare classification tasks (passing the list of dicts + columns to read) 25 | removed_sentiment = [{'review': x['review']} for x in reviews] 26 | tasks = skill.create_tasks(removed_sentiment) 27 | # Step 7: Save results to a JSONL file 28 | with open('tasks.jsonl', 'w') as jsonl_file: 29 | for entry in tasks: 30 | jsonl_file.write(json.dumps(entry) + '\n') 31 | 32 | if False: 33 | # Step 5: We could upload tasks file to Batch API 34 | batch_file = client.files.create( 35 | file=open('tasks.jsonl', "rb"), 36 | purpose="batch" 37 | ) 38 | batch_job = client.batches.create( 39 | input_file_id=batch_file.id, 40 | endpoint="/v1/chat/completions", 41 | completion_window="24h" 42 | ) 43 | batch_job = client.batches.retrieve(batch_job.id) 44 | print(batch_job) 45 | print('You will need this id to pull: ' + str(batch_job.output_file_id)) 46 | 47 | # AFTER 24h 48 | result_file_id = batch_job.output_file_id 49 | result = client.files.content(result_file_id).content 50 | 51 | result_file_name = "data/batch_job_results_movies.jsonl" 52 | 53 | with open(result_file_name, 'wb') as file: 54 | file.write(result) 55 | 56 | # Loading data from saved file 57 | results = [] 58 | with open(result_file_name, 'r') as file: 59 | for line in file: 60 | # Parsing the JSON string into a dict and appending to the list of results 61 | json_object = json.loads(line.strip()) 62 | results.append(json_object) 63 | # Reading only the first results 64 | 65 | for res in results: 66 | task_id = res['custom_id'] 67 | # Getting index from task id 68 | index = task_id.split('-')[-1] 69 | result = res['response']['body']['choices'][0]['message']['content'] 70 | review = reviews[int(index)] 71 | review['result'] = result 72 | 73 | with open('data_with_results.jsonl', 'w') as jsonl_file: 74 | for entry in results: 75 | jsonl_file.write(json.dumps(entry) + '\n') 76 | 77 | if __name__ == "__main__": 78 | main() -------------------------------------------------------------------------------- /examples/Code only examples/deepseek_inifinite_context.py: -------------------------------------------------------------------------------- 1 | import os 2 | from openai import OpenAI 3 | from flashlearn.skills.classification import ClassificationSkill 4 | 5 | 6 | def super_simple_chunker(text, chunk_size=1000): 7 | """Chunks text into smaller segments of specified size.""" 8 | chunks = [text[i:i + chunk_size] for i in range(0, len(text), chunk_size)] 9 | return [{"text": chunk} for chunk in chunks] 10 | 11 | 12 | def infinite_context(question, context): 13 | """ 14 | Uses a language model to provide answers to a given question 15 | by processing a large context into relevant chunks. 16 | """ 17 | # Step 1: Setup your provider 18 | 19 | # OpenAI setup - Uncomment if using OpenAI directly 20 | # model_name = "gpt-4o-mini" 21 | # client = OpenAI() 22 | # os.environ["OPENAI_API_KEY"] = 'Your api key' 23 | 24 | # DeepSeek setup 25 | model_name = 'deepseek-chat' 26 | client = OpenAI( 27 | api_key='YOUR DEEPSEEK API KEY', 28 | base_url="https://api.deepseek.com", 29 | ) 30 | 31 | # Step 2: Chunk context like you would with any RAG flow 32 | chunks = super_simple_chunker(context) 33 | 34 | # Step 3: Initialize the Classification Skill - Classify relevant content 35 | skill = ClassificationSkill( 36 | model_name=model_name, 37 | client=client, 38 | categories=["relevant", "somehow_relevant", "irrelevant"], 39 | max_categories=1, 40 | system_prompt="Classify content based on relevancy to the task" f"{question}", 41 | ) 42 | 43 | # Step 4: Prepare classification tasks (passing the list of dicts + columns to read) 44 | iterations = 0 45 | while len(chunks) > 64 or iterations > 2: 46 | tasks = skill.create_tasks(chunks) 47 | 48 | # Narrow down on quality context 49 | results = skill.run_tasks_in_parallel(tasks) 50 | print(results) 51 | 52 | # Step 6: Map results and reiterate if still too many chunks 53 | for i, review in enumerate(chunks): 54 | chunks[i]['category'] = results[str(i)]['categories'] 55 | chunks = [{'text': review['text']} for review in chunks if review['category'] == 'relevant'] 56 | 57 | iterations += 1 58 | 59 | # Answer 60 | answer = client.chat.completions.create( 61 | model=model_name, 62 | messages=[ 63 | {'role': 'user', 'content': str(chunks)}, 64 | {'role': 'user', 'content': question} 65 | ] 66 | ) 67 | 68 | return answer.choices[0].message.content 69 | 70 | 71 | if __name__ == "__main__": 72 | # Open the long context file 73 | file_path = 'context.txt' 74 | file = open(file_path, 'r', encoding='utf-8') 75 | 76 | # Read the file's contents 77 | file_contents = file.read() 78 | 79 | answer = infinite_context('YOUR QUESTION', file_contents) 80 | print(answer) -------------------------------------------------------------------------------- /examples/Code only examples/discover_and_classify_clusters.py: -------------------------------------------------------------------------------- 1 | from flashlearn.skills.classification import ClassificationSkill 2 | from flashlearn.skills import DiscoverLabelsSkill 3 | from flashlearn.utils import imdb_reviews_50k 4 | 5 | 6 | def main(): 7 | # Step 1: Provide your OpenAI 8 | # os.environ["OPENAI_API_KEY"] = 'YOUR API KEY' 9 | 10 | # Step 2: Load sample data 11 | data = imdb_reviews_50k(sample=100) 12 | 13 | # Step 3: Discover labels/clusters 14 | labeling_skill = DiscoverLabelsSkill( 15 | model_name="gpt-4o-mini", 16 | label_count=4, 17 | system_prompt="Uncover hidden themes in this movie reviews. Be funny", 18 | ) 19 | labeling_skill.save() 20 | 21 | # Step 4: Create tasks for discovering labels 22 | tasks = labeling_skill.create_tasks(data) 23 | results_labels = labeling_skill.run_tasks_in_parallel(tasks) 24 | labels = results_labels['0']['labels'] # Extract discovered labels 25 | 26 | # Step 5: Create a classification skill with discovered labels 27 | skill = ClassificationSkill( 28 | model_name="gpt-4o-mini", 29 | categories=labels, 30 | max_categories=1, 31 | system_prompt="We want to classify short movie reviews in listed categories", 32 | ) 33 | 34 | # Step 6: Build classification tasks 35 | tasks = skill.create_tasks(data) 36 | 37 | # Step 7: Run tasks in real-time 38 | results = skill.run_tasks_in_parallel(tasks) 39 | print(results) 40 | 41 | if __name__ == "__main__": 42 | main() -------------------------------------------------------------------------------- /examples/Code only examples/discover_themes_in_product_reviews.py: -------------------------------------------------------------------------------- 1 | import os 2 | from openai import OpenAI 3 | from flashlearn.skills.discover_labels import DiscoverLabelsSkill 4 | from flashlearn.skills.classification import ClassificationSkill 5 | 6 | def main(): 7 | 8 | os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY" 9 | 10 | # Example data (text reviews) 11 | 12 | text_reviews = [ 13 | 14 | {"comment": "Battery life exceeded expectations, though camera was mediocre."}, 15 | 16 | {"comment": "Arrived late and cracked screen, but customer support was helpful."} 17 | 18 | ] 19 | 20 | # Example data (images + brief text) 21 | 22 | # Here, the "image_base64" field simulates an encoded image 23 | 24 | image_reviews = [ 25 | 26 | {"image": "ENCODED_ISSUE_IMAGE", "comment": "WHZ BOTHER WITH IT?"}, 27 | 28 | {"image": "ENCODED_ISSUE_IMAGE", "comment": "This feature is amazing!! You should charge more!"} 29 | 30 | ] 31 | 32 | # 1) Label Discovery (Aggregates the entire dataset at once) 33 | 34 | discover_skill = DiscoverLabelsSkill(model_name="gpt-4o-mini", client=OpenAI()) 35 | 36 | column_modalities={"image":"image_base64", "comment": "text"} 37 | 38 | tasks_discover = discover_skill.create_tasks(text_reviews + image_reviews) 39 | 40 | discovered_labels = discover_skill.run_tasks_in_parallel(tasks_discover)['0']['labels'] 41 | 42 | print("Discovered labels:", discovered_labels) 43 | 44 | # 2) Classification using discovered labels 45 | 46 | classify_skill = ClassificationSkill(model_name="gpt-4o-mini", client=OpenAI(), categories=discovered_labels) 47 | 48 | tasks_classify = classify_skill.create_tasks(text_reviews + image_reviews) 49 | 50 | final_results = classify_skill.run_tasks_in_parallel(tasks_classify) 51 | 52 | print("Classification results:", final_results) 53 | 54 | if __name__ == "__main__": 55 | 56 | main() 57 | -------------------------------------------------------------------------------- /examples/Code only examples/image_classification.py: -------------------------------------------------------------------------------- 1 | import os 2 | from openai import OpenAI 3 | from flashlearn.skills.classification import ClassificationSkill 4 | from flashlearn.utils import cats_and_dogs 5 | 6 | def main(): 7 | # Step 1: Provide your OpenAI 8 | #os.environ["OPENAI_API_KEY"] = 'YOUR API KEY' 9 | 10 | # Step 2: Load sample image data in base64 format 11 | data = cats_and_dogs(sample=6) 12 | 13 | # Step 3: Initialize the Classification Skill 14 | skill = ClassificationSkill( 15 | model_name="gpt-4o-mini", 16 | client=OpenAI(), # Add the client if you want to specify OpenAI explicitly 17 | categories=["cat", "dog"], 18 | max_categories=1, 19 | system_prompt="We want to classify images by what is in the picture.", 20 | ) 21 | 22 | # Define which columns to treat as image data 23 | column_modalities = {"image_base64": "image_base64"} 24 | 25 | # Step 4: Prepare tasks from the test data 26 | tasks = skill.create_tasks( 27 | data, 28 | column_modalities=column_modalities 29 | ) 30 | 31 | # Step 5: Execute classification tasks 32 | results = skill.run_tasks_in_parallel(tasks) 33 | 34 | # Step 6: Map results back to the test DataFrame 35 | print(results) 36 | 37 | # Step 7: Save the Skill configuration 38 | skill.save("MyCustomSkillIMG.json") 39 | 40 | 41 | if __name__ == "__main__": 42 | main() -------------------------------------------------------------------------------- /examples/Code only examples/learn_new_skill.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flashlearn.skills.learn_skill import LearnSkill 4 | from flashlearn.utils import imdb_reviews_50k 5 | import plotly.express as px 6 | 7 | def main(): 8 | # Step 1: Provide your OpenAI 9 | # os.environ["OPENAI_API_KEY"] = 'YOUR API KEY' 10 | 11 | # Step 2: Initialize the LearnSkill 12 | learner = LearnSkill(model_name="gpt-4o-mini", verbose=True) 13 | 14 | # Step 3: Load sample data 15 | data = imdb_reviews_50k(sample=100) 16 | 17 | # Step 4: Learn the skill with the defined task 18 | skill = learner.learn_skill( 19 | data, 20 | task='Based on data sample define 3 thematic' 21 | ' categories satirical, quirky and absurd' 22 | ' Return category value on key "category"', 23 | model_name="gpt-4o-mini", 24 | ) 25 | 26 | # Step 5: Prepare and execute classification tasks 27 | tasks = skill.create_tasks(data) 28 | results = skill.run_tasks_in_parallel(tasks) 29 | 30 | print(results) 31 | 32 | if __name__ == "__main__": 33 | main() -------------------------------------------------------------------------------- /examples/Code only examples/learn_new_skill_img.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flashlearn.skills.learn_skill import LearnSkill 4 | from flashlearn.utils import cats_and_dogs 5 | import plotly.express as px 6 | 7 | def main(): 8 | # Step 1: Provide your OpenAI 9 | # os.environ["OPENAI_API_KEY"] = 'YOUR API KEY' 10 | 11 | # Step 2: Initialize the LearnSkill 12 | learner = LearnSkill(model_name="gpt-4o-mini", verbose=True) 13 | 14 | # Step 3: Load sample image data 15 | data = cats_and_dogs(sample=10) 16 | 17 | # Step 4: Learn the skill from images 18 | column_modalities = {"image_base64": "image_base64"} 19 | skill = learner.learn_skill( 20 | data[:3], 21 | task='Based on data sample define 4 thematic funny categories' 22 | ' Return category value on key "category"', 23 | columns=['image_base64'], 24 | model_name="gpt-4o-mini", 25 | column_modalities=column_modalities 26 | ) 27 | 28 | # Step 5: Prepare and execute classification tasks 29 | tasks = skill.create_tasks(data[3:], columns=['image_base64'], column_modalities=column_modalities) 30 | results = skill.run_tasks_in_parallel(tasks) 31 | 32 | # Step 6: Map results back to the test DataFrame 33 | print(results) 34 | if __name__ == "__main__": 35 | main() -------------------------------------------------------------------------------- /examples/Code only examples/load_skill.py: -------------------------------------------------------------------------------- 1 | from flashlearn.skills import GeneralSkill 2 | from flashlearn.skills.toolkit import ClassifyDifficultyOfQuestion 3 | from flashlearn.utils import imdb_reviews_50k 4 | 5 | 6 | def main(): 7 | # Step 1: Provide your OpenAI 8 | #os.environ["OPENAI_API_KEY"] = "API KEY" 9 | 10 | # Step 2: Load sample data 11 | reviews = imdb_reviews_50k(sample=50) 12 | 13 | # Step 3: Load the previously created ClassificationSkill 14 | skill = GeneralSkill.load_skill(ClassifyDifficultyOfQuestion) 15 | # Step 4: Build tasks from test data 16 | tasks = skill.create_tasks(reviews) 17 | # Step 5: Run tasks in real-time 18 | results = skill.run_tasks_in_parallel(tasks) 19 | print(results) 20 | 21 | 22 | if __name__ == "__main__": 23 | main() -------------------------------------------------------------------------------- /examples/Code only examples/load_skill_img.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | from flashlearn.skills import GeneralSkill 5 | from flashlearn.utils import cats_and_dogs 6 | 7 | 8 | def main(): 9 | # Step 1: Provide your OpenAI 10 | # os.environ["OPENAI_API_KEY"] = 'YOUR API KEY' 11 | 12 | # Step 2: Load sample image data 13 | pictures = cats_and_dogs(sample=6) 14 | 15 | script_dir = os.path.dirname(os.path.abspath(__file__)) 16 | file_path = os.path.join(script_dir, 'MyCustomSkillIMG.json') 17 | # Step 3: Load the previously created skill for images 18 | with open(file_path, "r") as file: 19 | data = json.load(file) 20 | skill = GeneralSkill.load_skill(data) 21 | 22 | # Step 4: Build tasks from test data 23 | column_modalities = {"image_base64": "image_base64"} 24 | tasks = skill.create_tasks(pictures, column_modalities=column_modalities) 25 | 26 | # Step 5: Run tasks in real-time 27 | results = skill.run_tasks_in_parallel(tasks) 28 | 29 | # Step 6: Map results to df_test 30 | print(results) 31 | 32 | if __name__ == "__main__": 33 | main() -------------------------------------------------------------------------------- /examples/Code only examples/perplexity_clone.py: -------------------------------------------------------------------------------- 1 | import os 2 | from openai import OpenAI 3 | from flashlearn.skills import GeneralSkill 4 | from flashlearn.skills.toolkit import ConvertToGoogleQueries, SimpleGoogleSearch 5 | 6 | os.environ["OPENAI_API_KEY"] = "" 7 | GOOGLE_API_KEY = "" 8 | GOOGLE_CSE_ID = "" 9 | MODEL_NAME = "gpt-4o-mini" 10 | 11 | question = 'When was python launched?' 12 | client = OpenAI() 13 | skill = GeneralSkill.load_skill(ConvertToGoogleQueries, client=client) 14 | queries = skill.run_tasks_in_parallel(skill.create_tasks([{"query": question}]))["0"] 15 | results = SimpleGoogleSearch(GOOGLE_API_KEY, GOOGLE_CSE_ID).search(queries['google_queries']) 16 | msgs = [ 17 | {"role": "system", "content": "insert links from search results in response to quote it"}, 18 | {"role": "user", "content": str(results)}, 19 | {"role": "user", "content": question}, 20 | ] 21 | print(client.chat.completions.create(model=MODEL_NAME, messages=msgs).choices[0].message.content) -------------------------------------------------------------------------------- /examples/Code only examples/sentiment_classification.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | from openai import OpenAI 5 | 6 | from flashlearn.skills.classification import ClassificationSkill 7 | from flashlearn.utils import imdb_reviews_50k 8 | 9 | 10 | def main(): 11 | # Step 1: Setup your provider 12 | #os.environ["OPENAI_API_KEY"] = 'YOUR API KEY' 13 | 14 | # Deepseek client 15 | #deep_seek = OpenAI( 16 | #api_key='YOUR DEEPSEEK API KEY', 17 | #base_url="https://api.deepseek.com", 18 | #) 19 | client = OpenAI() 20 | # Step 2: Load sample data (list of dicts with "review" and "sentiment" keys) 21 | reviews = imdb_reviews_50k(sample=100) 22 | 23 | # Step 3: Initialize the Classification Skill 24 | skill = ClassificationSkill( 25 | #model_name="deepseek-chat", 26 | model_name="gpt-4o-mini", 27 | client=client, 28 | categories=["positive", "negative"], 29 | max_categories=1, 30 | system_prompt="We want to classify short movie reviews by sentiment." 31 | ) 32 | # Step 4: Prepare classification tasks (passing the list of dicts + columns to read) 33 | removed_sentiment = [{'review': x['review']} for x in reviews] 34 | tasks = skill.create_tasks(removed_sentiment) 35 | # Step 7: Save results to a JSONL file 36 | with open('tasks.jsonl', 'w') as jsonl_file: 37 | for entry in tasks: 38 | jsonl_file.write(json.dumps(entry) + '\n') 39 | 40 | 41 | # Step 5: Execute classification tasks in real time ( We could upload file to Batch api) 42 | # We are using OpenAI tier 5 limits 43 | results = skill.run_tasks_in_parallel(tasks) 44 | 45 | # Step 6: Map results and check accuracy 46 | 47 | correct = 0 48 | for i, review in enumerate(reviews): 49 | reviews[i]['category'] = results[str(i)]['categories'] 50 | if reviews[i]['sentiment'] == results[str(i)]['categories']: 51 | correct += 1 52 | print(f'Accuracy: {round(correct / len(reviews), 2)} %') 53 | 54 | # Step 7: Save results to a JSONL file 55 | with open('results.jsonl', 'w') as jsonl_file: 56 | for entry in reviews: 57 | jsonl_file.write(json.dumps(entry) + '\n') 58 | 59 | # Step 8: Save the Skill configuration 60 | skill.save("BinaryClassificationSkill.json") 61 | 62 | 63 | 64 | if __name__ == "__main__": 65 | main() -------------------------------------------------------------------------------- /examples/Customer service/classify_tickets.md: -------------------------------------------------------------------------------- 1 | 2 | # Classifying Customer Service Requests 3 | ## Pro tip: Ctrl + C -> ChatGPT -> Ctrl + V -> Describe your problem-> Get your code 4 | --- 5 | 6 | ## Step 0: Imports and Environment Setup 7 | 8 | We start by importing the necessary libraries, setting any environment variables (e.g., API keys), and optionally creating a folder for storing JSON artifacts (skills, tasks, results). 9 | 10 | ### Environment Setup 11 | 12 | In this step, we import libraries, set up any credentials, and prepare folders for saving JSON artifacts. 13 | 14 | ```python 15 | import os 16 | import json 17 | 18 | # (Optional) If using OpenAI or another provider: 19 | os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY" 20 | 21 | # Create a folder for JSON artifacts 22 | json_folder = "json_artifacts" 23 | os.makedirs(json_folder, exist_ok=True) 24 | 25 | print("Step 0 complete: Environment setup.") 26 | ``` 27 | 28 | --- 29 | 30 | ## Step 1: Prepare or Create Customer Service Request Data 31 | 32 | We create a list of dictionaries, each representing a customer service request. In a real scenario, you might have these requests in a ticketing system. Here, each request has a “message” field containing the user’s query or complaint. 33 | 34 | ### Prepare Sample Customer Service Requests 35 | 36 | We define a list of dictionaries representing different kinds of requests. For simplicity, each has a “message” field, though you can include other metadata (ticket ID, customer ID, etc.). 37 | 38 | ```python 39 | service_requests = [ 40 | {"message": "I can't log into my account, please help!"}, 41 | {"message": "My order never arrived, I want a refund."}, 42 | {"message": "Where can I find instructions for setting up the device?"}, 43 | {"message": "I was charged twice for the same product."}, 44 | {"message": "How do I reset my password?"}, 45 | {"message": "The product is damaged and I want an exchange."} 46 | ] 47 | 48 | print("Step 1 complete: Sample data prepared.") 49 | print("Sample service request:", service_requests[0]) 50 | ``` 51 | 52 | --- 53 | 54 | ## Step 2: Define or Choose a Predefined Set of Categories 55 | 56 | We now specify the categories into which we want to classify service requests. For instance: 57 | • Account Issues 58 | • Shipping / Delivery Issues 59 | • Billing / Payment Issues 60 | • Product Support / Setup 61 | • Returns / Refunds 62 | 63 | You can tailor these categories to match your actual customer support process. 64 | 65 | ### Define Predefined Categories 66 | 67 | We create a list of categories that we’ll use to classify each request. 68 | 69 | ```python 70 | predefined_categories = [ 71 | "Account Issues", 72 | "Shipping / Delivery Issues", 73 | "Billing / Payment Issues", 74 | "Product Support / Setup", 75 | "Returns / Refunds" 76 | ] 77 | 78 | print("Step 2 complete: Predefined categories specified.") 79 | print("Categories:", predefined_categories) 80 | ``` 81 | 82 | --- 83 | 84 | ## Step 3: Create or Configure a Classification Skill 85 | 86 | We will use flashlearn to create a “ClassificationSkill” specifying our predefined categories. This ensures each request is assigned to one or more relevant categories. 87 | 88 | ### Define a Classification Skill 89 | 90 | We create a ClassificationSkill and provide it with our predefined categories. In a real setting, you could also include a custom prompt or instructions to help the model make decisions. 91 | 92 | ```python 93 | from flashlearn.skills.classification import ClassificationSkill 94 | from openai import OpenAI 95 | 96 | def create_classification_skill(categories): 97 | # Create a client (OpenAI, for instance) 98 | client = OpenAI() 99 | 100 | classify_skill = ClassificationSkill( 101 | model_name="gpt-4o-mini", # Example model name 102 | client=client, 103 | categories=categories 104 | ) 105 | return classify_skill 106 | 107 | classification_skill = create_classification_skill(predefined_categories) 108 | print("Step 3 complete: Classification skill created.") 109 | ``` 110 | 111 | --- 112 | 113 | ## Step 4: Save the Classification Skill as JSON (Optional) 114 | 115 | We can store the classification skill definition in JSON, so we don’t need to recreate it every time. 116 | 117 | ### Store the Classification Skill (Optional) 118 | 119 | This preserves the skill’s definition in a JSON file, enabling re-loading as needed. 120 | 121 | ```python 122 | skill_path = os.path.join(json_folder, "customer_service_classification_skill.json") 123 | classification_skill.save(skill_path) 124 | print(f"Step 4 complete: Skill saved to {skill_path}") 125 | ``` 126 | 127 | --- 128 | 129 | ## Step 5: Load the Classification Skill 130 | 131 | When we want to classify data, we simply load the skill from JSON rather than rebuilding it. This is optional if you just created the skill in the same session. 132 | 133 | ### Load the Saved Skill 134 | 135 | We restore the skill from JSON for immediate usage. 136 | 137 | ```python 138 | from flashlearn.skills import GeneralSkill 139 | skill_path = os.path.join(json_folder, "customer_service_classification_skill.json") 140 | with open(skill_path, "r", encoding="utf-8") as file: 141 | definition = json.load(file) 142 | loaded_classification_skill = GeneralSkill.load_skill(definition) 143 | print("Step 5 complete: Skill loaded from JSON:", loaded_classification_skill) 144 | ``` 145 | 146 | --- 147 | 148 | ## Step 6: Create Classification Tasks 149 | 150 | We transform each service request into a “task” that the skill can process. Because this is text data, we can specify the “message” field as having a text modality. 151 | 152 | ### Create Tasks for Classification 153 | 154 | We show the skill which fields it should treat as text. Here, “message” is our primary text field. 155 | 156 | ```python 157 | column_modalities = { 158 | "message": "text" 159 | } 160 | 161 | classification_tasks = loaded_classification_skill.create_tasks( 162 | service_requests, 163 | column_modalities=column_modalities 164 | ) 165 | 166 | print("Step 6 complete: Classification tasks created.") 167 | print("Sample classification task:", classification_tasks[0]) 168 | ``` 169 | 170 | --- 171 | 172 | ## Step 7: (Optional) Save Classification Tasks to JSONL 173 | 174 | If needed, store the tasks in a JSONL file for offline processing or reproducibility. 175 | 176 | ### Save Classification Tasks (Optional) 177 | 178 | We store tasks to JSONL for auditing or offline usage. 179 | 180 | ```python 181 | classification_tasks_path = os.path.join(json_folder, "service_requests_tasks.jsonl") 182 | with open(classification_tasks_path, 'w') as f: 183 | for task in classification_tasks: 184 | f.write(json.dumps(task) + '\n') 185 | 186 | print(f"Step 7 complete: Classification tasks saved to {classification_tasks_path}") 187 | ``` 188 | 189 | --- 190 | 191 | ## Step 8: Load Classification Tasks from JSONL (Optional) 192 | 193 | You can reload the tasks later in a different environment, if desired. 194 | 195 | ### Load Classification Tasks from JSONL (Optional) 196 | 197 | Demonstrates how to retrieve tasks from a JSONL file for classification. 198 | 199 | ```python 200 | loaded_classification_tasks = [] 201 | with open(classification_tasks_path, 'r') as f: 202 | for line in f: 203 | loaded_classification_tasks.append(json.loads(line)) 204 | 205 | print("Step 8 complete: Classification tasks reloaded.") 206 | print("Example reloaded task:", loaded_classification_tasks[0]) 207 | ``` 208 | 209 | --- 210 | 211 | ## Step 9: Run the Classification 212 | 213 | We now run the classification tasks through the loaded skill. The AI model assigns each request to one or more predefined categories. 214 | 215 | ### Classify the Service Requests 216 | 217 | We execute the tasks in parallel (or sequentially) and observe the category assignments for each request. 218 | 219 | ```python 220 | classification_results = loaded_classification_skill.run_tasks_in_parallel(loaded_classification_tasks) 221 | 222 | print("Step 9 complete: Classification finished.") 223 | print("Sample result (task_id, output):") 224 | for task_id, cat_data in list(classification_results.items())[:1]: 225 | print(f" Task ID {task_id}: {cat_data}") 226 | ``` 227 | 228 | --- 229 | 230 | ## Step 10: Map the Classification Results Back to the Original Data 231 | 232 | We attach each classification result to the corresponding service request object. This way, every request now has a “classification” field that indicates the assigned category or categories. 233 | 234 | ### Integrate Classification Output into Original Requests 235 | 236 | We match each result to the appropriate service request for a final, annotated dataset. 237 | 238 | ```python 239 | annotated_requests = [] 240 | for task_id_str, output_json in classification_results.items(): 241 | task_id = int(task_id_str) 242 | service_request = service_requests[task_id] 243 | service_request["classification"] = output_json 244 | annotated_requests.append(service_request) 245 | 246 | print("Step 10 complete: Mapped classification output to requests.") 247 | print("Sample annotated request:", annotated_requests[0]) 248 | ``` 249 | 250 | --- 251 | 252 | ## Step 11: Store the Final Classified Requests (Optional) 253 | 254 | We can store the final labeled data in a JSONL file or other format for integration with helpdesk systems, further analysis, or dashboards. 255 | 256 | ### Store the Final Labeled Requests (Optional) 257 | 258 | Finally, we save the annotated service requests to a JSONL file, ensuring we can ingest them into a CRM, BI tools, or analytics pipeline. 259 | 260 | ```python 261 | final_requests_path = os.path.join(json_folder, "service_requests_classified.jsonl") 262 | with open(final_requests_path, 'w') as f: 263 | for req in annotated_requests: 264 | f.write(json.dumps(req) + '\n') 265 | 266 | print(f"Step 11 complete: Labeled customer service requests saved to {final_requests_path}") 267 | ``` 268 | 269 | --- 270 | 271 | ## Summary and Next Steps 272 | 273 | In this tutorial, we: 274 | 275 | 1. Created a set of typical customer service requests (text-based). 276 | 2. Specified a list of predefined categories (e.g., Account Issues, Shipping Issues, Billing, Product Support, Returns). 277 | 3. Built a ClassificationSkill to map each request to the relevant category or categories. 278 | 4. Stored tasks and results in JSON/JSONL for reproducibility. 279 | 5. Mapped classification results back to the original requests for integrated data. 280 | 281 | You can extend this approach by incorporating more sophisticated prompts, additional categories, or advanced logic (e.g., multi-label classification, confidence scores). This completes our guide for classifying customer service requests into predefined categories! 282 | -------------------------------------------------------------------------------- /examples/Finance/parse_financial_report_data.md: -------------------------------------------------------------------------------- 1 | # Automated Financial Data Extraction 2 | --- 3 | In this guide, we’ll walk through setting up an AI-driven workflow that reads a company’s yearly financial report and turns it into structured data. From building and saving a custom extraction skill to mapping the AI outputs back to your original documents, we’ll cover each step to get you from raw text to meaningful numbers. 4 | 5 | ## Step 0: Imports and Environment Setup 6 | 7 | First, we import our necessary libraries, set any environment variables (e.g., API keys), and create a folder to store JSON artifacts (e.g., skills, tasks, final results). 8 | 9 | ### Environment Setup 10 | 11 | We import libraries, optionally define credentials, and create a directory to store JSON artifacts that keep our workflow reproducible. 12 | 13 | ```python 14 | import os 15 | import json 16 | 17 | # (Optional) If using OpenAI or another provider: 18 | os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY" 19 | 20 | # Create a folder for JSON artifacts 21 | json_folder = "json_artifacts" 22 | os.makedirs(json_folder, exist_ok=True) 23 | 24 | print("Step 0 complete: Environment setup.") 25 | ``` 26 | 27 | --- 28 | 29 | ## Step 1: Prepare Financial Text Excerpts 30 | 31 | We create synthetic text excerpts that resemble sections from a yearly financial report. Each excerpt might contain references to gross profit, EBITDA, net income, or other metrics. You can expand or replace these with real data as needed. 32 | 33 | ### Prepare or Generate Yearly Report Data 34 | 35 | We define a list of text entries mimicking sections of a company's yearly financial report. Each entry is a short paragraph referencing various financial metrics. 36 | 37 | ```python 38 | financial_texts = [ 39 | { 40 | "report_section": ( 41 | "In 2022, the company reported a gross profit of $1.2B, an EBITDA of $900M, " 42 | "and a net income of $500M. Operating expenses increased slightly compared to 2021." 43 | ) 44 | }, 45 | { 46 | "report_section": ( 47 | "For the fiscal year 2023, gross profit grew to $1.5B, while net income reached $650M. " 48 | "EBITDA was reported at approximately $1.1B. The company also introduced new products." 49 | ) 50 | }, 51 | { 52 | "report_section": ( 53 | "Our consolidated statement shows that total revenue surpassed $3B, with a gross profit " 54 | "of $1.8B, and EBITDA climbed to $1.2B. Net income remained around $700M this year." 55 | ) 56 | } 57 | ] 58 | 59 | print("Step 1 complete: Financial text data prepared.") 60 | print("Sample text entry:", financial_texts[0]) 61 | ``` 62 | 63 | --- 64 | 65 | ## Step 2: Define an AI Skill for Financial Data Extraction 66 | 67 | We’ll use flashlearn’s approach to create a skill that extracts structured data points like gross profit, EBITDA, and net income from each text record. 68 | 69 | ### Define a “FinancialExtractionSkill” 70 | 71 | We instruct the AI to parse each text for specific metrics (gross profit, EBITDA, net income) and return these as structured JSON. In a real scenario, you can add more or adjust them as needed. 72 | 73 | ```python 74 | from flashlearn.skills.learn_skill import LearnSkill 75 | from flashlearn.skills import GeneralSkill 76 | 77 | def create_financial_extraction_skill(): 78 | learner = LearnSkill( 79 | model_name="gpt-4o-mini", # Example model name 80 | verbose=True 81 | ) 82 | 83 | # Instruction/prompt outline: 84 | extraction_instruction = ( 85 | "You are an AI system that extracts financial metrics. For each report_section, " 86 | "please return a JSON with keys: 'gross_profit', 'ebitda', 'net_income'. " 87 | "Provide values as floats in millions. If a metric is missing, set it to a default (e.g., 0)." 88 | ) 89 | 90 | skill = learner.learn_skill( 91 | df=[], # You could pass examples if you do not get desired accuracy 92 | task=extraction_instruction, 93 | model_name="gpt-4o-mini" 94 | ) 95 | 96 | return skill 97 | 98 | financial_skill = create_financial_extraction_skill() 99 | print("Step 2 complete: Financial extraction skill defined and created.") 100 | ``` 101 | 102 | --- 103 | 104 | ## Step 3: Store the Learned Skill as JSON 105 | 106 | We can serialize the newly learned skill (which includes prompts, model configurations, etc.) into a JSON file for reproducibility or reuse in other environments. 107 | 108 | ### Store the Learned Skill 109 | 110 | This ensures we don’t need to re-learn the skill each time, preserving the instructions and settings. 111 | 112 | ```python 113 | financial_skill_path = os.path.join(json_folder, "financial_extraction_skill.json") 114 | financial_skill.save(financial_skill_path) 115 | print(f"Step 3 complete: Skill saved to {financial_skill_path}") 116 | ``` 117 | 118 | --- 119 | 120 | ## Step 4: Load the Saved Skill 121 | 122 | We can load the skill whenever we want to run the extraction without re-learning. 123 | 124 | ### Load the Saved Skill 125 | 126 | We demonstrate how to restore the skill from the JSON file. 127 | 128 | ```python 129 | financial_skill_path = os.path.join(json_folder, "financial_extraction_skill.json") 130 | with open(financial_skill_path, "r", encoding="utf-8") as file: 131 | definition = json.load(file) 132 | loaded_financial_skill = GeneralSkill.load_skill(definition) 133 | print("Step 4 complete: Skill loaded from JSON:", loaded_financial_skill) 134 | ``` 135 | 136 | --- 137 | 138 | ## Step 5: Create Extraction Tasks 139 | 140 | We now create tasks from each text snippet, letting the skill know which columns to read as text. Because our data has “report_section” fields, we’ll map that field to text. 141 | 142 | ### Create Tasks for Extraction 143 | 144 | We transform each excerpt into a task that the AI skill can process. In this case, “report_section” is categorized as text. 145 | 146 | ```python 147 | column_modalities = { 148 | "report_section": "text" 149 | } 150 | 151 | extraction_tasks = loaded_financial_skill.create_tasks( 152 | financial_texts, 153 | column_modalities=column_modalities 154 | ) 155 | 156 | print("Step 5 complete: Extraction tasks created.") 157 | print("Sample extraction task:", extraction_tasks[0]) 158 | ``` 159 | 160 | --- 161 | 162 | ## Step 6: (Optional) Save Extraction Tasks to JSONL 163 | 164 | It’s often helpful to store tasks in JSONL format so you can process them offline or in a separate environment. 165 | 166 | ### Save Extraction Tasks to JSONL (Optional) 167 | 168 | Storing the tasks ensures an auditable chain of how each record is processed. 169 | 170 | ```python 171 | extraction_tasks_path = os.path.join(json_folder, "financial_extraction_tasks.jsonl") 172 | with open(extraction_tasks_path, 'w') as f: 173 | for task in extraction_tasks: 174 | f.write(json.dumps(task) + '\n') 175 | 176 | print(f"Step 6 complete: Extraction tasks saved to {extraction_tasks_path}") 177 | ``` 178 | 179 | --- 180 | 181 | ## Step 7: Load Extraction Tasks from JSONL (Optional) 182 | 183 | If desired, load the tasks again from JSONL—useful when you’re running them in a new session or environment. 184 | 185 | ### Load Extraction Tasks from JSONL (Optional) 186 | 187 | This demonstrates how to recreate the tasks from the stored JSONL file. 188 | 189 | ```python 190 | loaded_extraction_tasks = [] 191 | with open(extraction_tasks_path, 'r') as f: 192 | for line in f: 193 | loaded_extraction_tasks.append(json.loads(line)) 194 | 195 | print("Step 7 complete: Extraction tasks reloaded.") 196 | print("Sample reloaded task:", loaded_extraction_tasks[0]) 197 | ``` 198 | 199 | --- 200 | 201 | ## Step 8: Run the Financial Data Extraction 202 | 203 | We run the tasks through the loaded skill. The AI extracts numerical values for the specified metrics from each text snippet. 204 | 205 | ### Execute Data Extraction 206 | 207 | We run the tasks in parallel (or sequentially), obtaining structured financial metrics for each text record. 208 | 209 | ```python 210 | extraction_results = loaded_financial_skill.run_tasks_in_parallel(loaded_extraction_tasks) 211 | 212 | print("Step 8 complete: Data extraction finished.") 213 | print("Sample result (task_id, output):") 214 | for task_id, extracted_data in list(extraction_results.items())[:1]: 215 | print(f" Task ID {task_id}: {extracted_data}") 216 | ``` 217 | 218 | --- 219 | 220 | ## Step 9: Map the Extraction Output Back to the Original Data 221 | 222 | We attach each extraction output (the structured metrics) to its corresponding text entry, producing a final annotated dataset. 223 | 224 | ### Integrate Extraction Results with Original Data 225 | 226 | We map each result by task ID, preserving the link between source text and extracted metrics. 227 | 228 | ```python 229 | annotated_financials = [] 230 | for task_id_str, output_json in extraction_results.items(): 231 | task_id = int(task_id_str) 232 | record = financial_texts[task_id] 233 | record["extracted_metrics"] = output_json 234 | annotated_financials.append(record) 235 | 236 | print("Step 9 complete: Mapped extraction output to financial data.") 237 | print("Annotated record example:", annotated_financials[0]) 238 | ``` 239 | 240 | --- 241 | 242 | ## Step 10: Store the Final Annotated Results 243 | 244 | Finally, we can store our annotated financial data in a JSONL file for further analysis, integration with trading algorithms, or additional forecasting. 245 | 246 | ### Save the Annotated Financial Data 247 | 248 | We write the updated records (including extracted metrics) to a JSONL file, ready for automated reporting or integration with downstream applications. 249 | 250 | ```python 251 | final_extraction_path = os.path.join(json_folder, "financial_extraction_results.jsonl") 252 | with open(final_extraction_path, 'w') as f: 253 | for entry in annotated_financials: 254 | f.write(json.dumps(entry) + '\n') 255 | 256 | print(f"Step 10 complete: Final extracted data saved to {final_extraction_path}") 257 | ``` 258 | 259 | --- 260 | 261 | ## Summary and Next Steps 262 | 263 | Using these steps, you can: 264 | 265 | - Generate or load textual representations of a yearly financial report. 266 | - Train (or configure) an AI skill to identify and extract specific metrics like gross profit, EBITDA, net income, and so on. 267 | - Store all tasks and skill definitions in JSON or JSONL for auditable, reproducible workflows. 268 | - Integrate the extracted data with automated trading or forecasting systems, or produce structured financial reporting for stakeholders. 269 | 270 | Next steps might include: 271 | 272 | - Expanding the set of financial metrics you parse. 273 | - Combining extraction with summarization tasks to produce automated commentary for each report. 274 | 275 | This completes our example for using AI to extract structured financial data (gross profit, EBITDA, net income, etc.) from yearly report text! 276 | -------------------------------------------------------------------------------- /examples/Marketing/customer_segmentation.md: -------------------------------------------------------------------------------- 1 | # Improve Customer Segmentation 2 | --- 3 | Want to quickly pinpoint which of your customers are “High-value,” “At-risk,” or “Occasional”? In this walkthrough, we’ll generate synthetic customer data, train an AI segmentation skill, and map each customer to the right category with a clear rational. 4 | ## Step 0: Imports and Environment Setup 5 | 6 | In this step, we import the necessary libraries, optionally set environment variables (like API keys), and prepare a folder for storing JSON artifacts (e.g., skills, tasks, final segmentation results). 7 | 8 | ### Environment Setup 9 | 10 | Import libraries, set up any credentials, and prepare folders for saving JSON artifacts. 11 | 12 | ```python 13 | import os 14 | import json 15 | 16 | # (Optional) If using OpenAI or another provider: 17 | os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY" 18 | 19 | # Create a folder for JSON artifacts 20 | json_folder = "json_artifacts" 21 | os.makedirs(json_folder, exist_ok=True) 22 | 23 | print("Step 0 complete: Environment setup.") 24 | ``` 25 | 26 | --- 27 | 28 | ## Step 1: Prepare or Generate Synthetic Customer Data 29 | 30 | Here, we'll create some synthetic customer data. Each record might represent a customer’s engagement and purchase patterns. For illustration, each record can have: 31 | - A unique customer ID 32 | - Average monthly spend 33 | - Number of monthly purchases 34 | - Whether or not they use premium features 35 | - A textual summary or note 36 | 37 | ### Prepare Synthetic Customer Data 38 | 39 | We define a list of dictionaries representing each customer’s key metrics. 40 | 41 | ```python 42 | import random 43 | 44 | def generate_synthetic_customers(num_customers=10): 45 | data = [] 46 | for i in range(num_customers): 47 | monthly_spend = round(random.uniform(20, 200), 2) 48 | purchases = random.randint(0, 15) 49 | uses_premium = random.choice([True, False]) 50 | notes = "High engagement" if purchases > 10 else "Moderate engagement" 51 | 52 | data.append({ 53 | "customer_id": f"CUST_{i:03d}", 54 | "monthly_spend": monthly_spend, 55 | "monthly_purchases": purchases, 56 | "uses_premium": uses_premium, 57 | "notes": notes 58 | }) 59 | return data 60 | 61 | customer_data = generate_synthetic_customers(12) 62 | 63 | print("Step 1 complete: Synthetic customer data generated.") 64 | print("Sample customer record:", customer_data[0]) 65 | ``` 66 | 67 | --- 68 | 69 | ## Step 2: Define an AI Skill for Segmentation 70 | 71 | We will use flashlearn to create or learn a skill that segments customers into a fixed set of categories (e.g., “High-value,” “At-risk,” “Occasional,” etc.), or returns a new suggested segmentation based on patterns observed in the data. In a real scenario, you might refine instructions, add domain knowledge, or provide explicit examples. 72 | 73 | ### Define a Segmentation Skill 74 | 75 | We simulate a skill that reads each customer’s data and outputs a segmented category, plus a brief rationale. 76 | 77 | ```python 78 | from flashlearn.skills.learn_skill import LearnSkill 79 | from flashlearn.skills import GeneralSkill 80 | 81 | def create_segmentation_skill(): 82 | learner = LearnSkill( 83 | model_name="gpt-4o-mini", # Example model name 84 | verbose=True 85 | ) 86 | 87 | # Example instruction for segmentation 88 | segmentation_instruction = ( 89 | "You are an AI system that analyzes customer data for segmentation. Describe also data ranges in description for given dataset " 90 | "For each record, consider monthly_spend, monthly_purchases, uses_premium, and notes. " 91 | "Return JSON with the following keys:\n" 92 | "- 'segment': the recommended segment (e.g., 'High-value,' 'Occasional,' 'At-risk,' etc.)\n" 93 | "- 'rationale': brief explanation of why the user fits that segment." 94 | ) 95 | 96 | skill = learner.learn_skill( 97 | customer_data, 98 | task=segmentation_instruction, 99 | model_name="gpt-4o-mini" 100 | ) 101 | 102 | return skill 103 | 104 | segmentation_skill = create_segmentation_skill() 105 | print("Step 2 complete: Segmentation skill defined and created.") 106 | ``` 107 | 108 | --- 109 | 110 | ## Step 3: Store the Learned Skill as JSON 111 | 112 | We save the newly created segmentation skill—including its model parameters and prompt instructions—in JSON for re-use. 113 | 114 | ### Store the Learned Skill in JSON 115 | 116 | This preserves the skill definition so it can be loaded without re-learning. 117 | 118 | ```python 119 | segmentation_skill_path = os.path.join(json_folder, "customer_segmentation_skill.json") 120 | segmentation_skill.save(segmentation_skill_path) 121 | print(f"Step 3 complete: Skill saved to {segmentation_skill_path}") 122 | ``` 123 | 124 | --- 125 | 126 | ## Step 4: Load the Saved Skill 127 | 128 | To actually perform segmentation in the future, we can load the skill from the JSON file, bypassing the need to re-learn. 129 | 130 | ### Load the Saved Skill 131 | 132 | We restore the skill from JSON for use in processing data. 133 | 134 | ```python 135 | segmentation_skill_path = os.path.join(json_folder, "customer_segmentation_skill.json") 136 | with open(segmentation_skill_path, "r", encoding="utf-8") as file: 137 | definition = json.load(file) 138 | loaded_segmentation_skill = GeneralSkill.load_skill(definition) 139 | print("Step 4 complete: Skill loaded from JSON:", loaded_segmentation_skill) 140 | ``` 141 | 142 | --- 143 | 144 | ## Step 5: Create Segmentation Tasks 145 | 146 | We transform each customer record into a “task” the skill can process. Generally, each record includes numerical or boolean fields, plus textual notes. We inform the skill which fields to consider as “text” if needed. 147 | 148 | ### Create Tasks for Segmentation 149 | 150 | We convert the customer data into tasks for the segmentation skill. 151 | 152 | ```python 153 | # For demonstration, treat 'notes' as text; the rest remain numeric/boolean. 154 | column_modalities = { 155 | "notes": "text" 156 | } 157 | 158 | tasks_segment = loaded_segmentation_skill.create_tasks( 159 | customer_data, 160 | column_modalities=column_modalities 161 | ) 162 | 163 | print("Step 5 complete: Segmentation tasks created.") 164 | print("Sample segmentation task:", tasks_segment[0]) 165 | ``` 166 | 167 | --- 168 | 169 | ## Step 6: (Optional) Save Segmentation Tasks to JSONL 170 | 171 | We can store these tasks in a JSONL file for offline or separate environment processing. 172 | 173 | ### Save Segmentation Tasks to JSONL (Optional) 174 | 175 | This helps maintain a record of task creation for reproducible workflows. 176 | 177 | ```python 178 | tasks_segment_jsonl_path = os.path.join(json_folder, "segmentation_tasks.jsonl") 179 | with open(tasks_segment_jsonl_path, 'w') as f: 180 | for task in tasks_segment: 181 | f.write(json.dumps(task) + '\n') 182 | 183 | print(f"Step 6 complete: Segmentation tasks saved to {tasks_segment_jsonl_path}") 184 | ``` 185 | 186 | --- 187 | 188 | ## Step 7: Load Segmentation Tasks from JSONL (Optional) 189 | 190 | If needed, load tasks again from JSONL. This is useful when tasks are generated in one environment and processed in another. 191 | 192 | ### Load Segmentation Tasks from JSONL (Optional) 193 | 194 | Shows how to retrieve tasks from the JSONL file so they can be processed. 195 | 196 | ```python 197 | loaded_segmentation_tasks = [] 198 | with open(tasks_segment_jsonl_path, 'r') as f: 199 | for line in f: 200 | loaded_segmentation_tasks.append(json.loads(line)) 201 | 202 | print("Step 7 complete: Segmentation tasks reloaded from JSONL.") 203 | print("Example loaded segmentation task:", loaded_segmentation_tasks[0]) 204 | ``` 205 | 206 | --- 207 | 208 | ## Step 8: Run the Segmentation 209 | 210 | We now pass the segmentation tasks to our loaded skill. The AI produces a category and a rationale for each customer. 211 | 212 | ### Run Segmentation 213 | 214 | We execute the tasks in parallel (or sequentially) to receive the recommended customer segment and rationale. 215 | 216 | ```python 217 | segmentation_results = loaded_segmentation_skill.run_tasks_in_parallel(loaded_segmentation_tasks) 218 | 219 | print("Step 8 complete: Segmentation finished.") 220 | print("Sample result (task_id, output):") 221 | for task_id, seg_data in list(segmentation_results.items())[:1]: 222 | print(f" Task ID {task_id}: {seg_data}") 223 | ``` 224 | 225 | --- 226 | 227 | ## Step 9: Map the Segmentation Output Back to the Customer Data 228 | 229 | We attach each output (segment, rationale) back onto the corresponding customer record, facilitating analysis or additional business logic. 230 | 231 | ### Integrate Segmentation Results into Original Data 232 | 233 | We match each task’s result to the corresponding customer record to produce a final annotated dataset. 234 | 235 | ```python 236 | annotated_customers = [] 237 | for task_id_str, output_json in segmentation_results.items(): 238 | task_id = int(task_id_str) 239 | record = customer_data[task_id] 240 | record["segmentation"] = output_json 241 | annotated_customers.append(record) 242 | 243 | print("Step 9 complete: Mapped segmentation output to customer data.") 244 | print("Sample annotated customer:", annotated_customers[0]) 245 | ``` 246 | 247 | --- 248 | 249 | ## Step 10: Store the Final Annotated Results 250 | 251 | Finally, we write these annotated customer records (including the newly assigned segments) to a JSONL or other file for consumption by other systems, dashboards, or additional modeling. 252 | 253 | ### Store the Segmented Customer Data 254 | 255 | We save the annotated customer records to a JSONL file for future use or integration with other pipelines. 256 | 257 | ```python 258 | final_segments_path = os.path.join(json_folder, "customer_segmentation_results.jsonl") 259 | with open(final_segments_path, 'w') as f: 260 | for cust in annotated_customers: 261 | f.write(json.dumps(cust) + '\n') 262 | 263 | print(f"Step 10 complete: Final customer segmentation results saved to {final_segments_path}") 264 | ``` 265 | 266 | --- 267 | 268 | ## Summary and Next Steps 269 | 270 | This tutorial demonstrates a straightforward approach to AI-driven customer segmentation: 271 | 272 | 1. We generated synthetic customer data reflecting monthly spend, purchase frequency, and a textual “notes” column. 273 | 2. We trained or configured a skill to segment customers into categories, also providing a rationale. 274 | 3. We stored and loaded the skill in JSON, created tasks, saved those tasks in JSONL, and loaded them again if needed. 275 | 4. We captured the final annotated data (segments and rationales) in a JSONL file. 276 | 277 | You can extend or refine this approach by: 278 | - Providing more detailed prompts or examples in the skill training step to handle complex real-world behaviors. 279 | - Incorporating numerical thresholds or advanced logic for certain segments. 280 | - Building subsequent tasks (e.g., marketing campaigns or personalized offers) using the assigned segments. 281 | 282 | This completes our guide for using AI to improve customer segmentation! 283 | -------------------------------------------------------------------------------- /examples/Product intelligence/discover_trends_in_prodcut _reviews.md: -------------------------------------------------------------------------------- 1 | # Discovering Themes in Product Reviews 2 | 3 | In this guide, we’ll show how to automatically discover labels or categories, then classify each review at scale. This saves time and helps uncover hidden insights in your feedback data quickly. 4 | 5 | --- 6 | 7 | ## Step 0: Imports and Environment Setup 8 | 9 | Create or open a new Jupyter Notebook. Start by importing the necessary libraries, setting up any environment variables (e.g., API keys), and optionally creating a folder for storing JSON artifacts. 10 | 11 | ### Environment Setup 12 | 13 | In this step, we import libraries, set any needed environment variables, and optionally create a folder for JSON artifacts (e.g., skills, tasks, results). 14 | 15 | ```python 16 | import os 17 | import json 18 | from openai import OpenAI 19 | from flashlearn.skills.discover_labels import DiscoverLabelsSkill 20 | from flashlearn.skills.classification import ClassificationSkill 21 | 22 | # (Optional) Set your OpenAI API key 23 | os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY" 24 | 25 | # (Optional) Create a folder for JSON artifacts 26 | json_folder = "json_artifacts" 27 | os.makedirs(json_folder, exist_ok=True) 28 | 29 | print("Step 0 complete: Imports and environment setup.") 30 | ``` 31 | 32 | --- 33 | 34 | ## Step 1: Prepare or Generate Text Data 35 | 36 | We define a simple list of dictionaries with text-based reviews. Each entry has a "comment" field for demonstration purposes. 37 | 38 | ### Prepare Text Data 39 | 40 | In this step, we compose a list of text reviews or sentences to demonstrate label discovery and classification. 41 | 42 | ```python 43 | # Example text reviews 44 | text_reviews = [ 45 | {"comment": "Battery life exceeded expectations, though camera was mediocre."}, 46 | {"comment": "Arrived late, but customer service was supportive."}, 47 | {"comment": "The product is overpriced. I want a refund."}, 48 | {"comment": "Fantastic design and smooth performance."}, 49 | {"comment": "Shipping was incredibly fast, but packaging could be improved."} 50 | ] 51 | 52 | print("Step 1 complete: Data prepared.") 53 | print("Sample data:", text_reviews) 54 | ``` 55 | 56 | --- 57 | 58 | ## Step 2: Initialize and Define the Label Discovery Skill 59 | 60 | Label discovery is useful for automatically inferring potential categories or topics from the entire dataset. 61 | 62 | ### Define the Label Discovery Skill 63 | 64 | Here, we use the “DiscoverLabelsSkill” to find relevant labels from the dataset in a single pass. We supply a model name and an OpenAI client. 65 | 66 | ```python 67 | def get_discover_skill(): 68 | # Create a client (OpenAI in this example) 69 | client = OpenAI() 70 | 71 | discover_skill = DiscoverLabelsSkill( 72 | model_name="gpt-4o-mini", 73 | client=client 74 | ) 75 | return discover_skill 76 | 77 | discover_skill = get_discover_skill() 78 | print("Step 2 complete: Label discovery skill initialized.") 79 | ``` 80 | 81 | --- 82 | 83 | ## Step 3: Create Label Discovery Tasks 84 | 85 | We transform our list of text reviews into the “tasks” format expected by DiscoverLabelsSkill. Since this is text-only data, our column modalities can either be omitted or simply mark “comment” as text. 86 | 87 | ### Create Discovery Tasks 88 | 89 | We convert the text data into tasks that the discovery skill can process. For text-only fields, we pass “comment” as having a “text” modality. 90 | 91 | ```python 92 | column_modalities = { 93 | "comment": "text" 94 | } 95 | 96 | # Create discovery tasks 97 | tasks_discover = discover_skill.create_tasks( 98 | text_reviews, 99 | column_modalities=column_modalities 100 | ) 101 | 102 | print("Step 3 complete: Discovery tasks created.") 103 | print("Sample discovery task:", tasks_discover[0]) 104 | ``` 105 | 106 | --- 107 | 108 | ## Step 4: (Optional) Save Discovery Tasks to JSONL 109 | 110 | Although optional, saving tasks to JSONL allows for offline processing or auditing. 111 | 112 | ### Save Discovery Tasks to JSONL (Optional) 113 | 114 | This helps maintain reproducibility and a clear record of how tasks were generated. 115 | 116 | ```python 117 | tasks_discover_jsonl_path = os.path.join(json_folder, "discovery_tasks.jsonl") 118 | with open(tasks_discover_jsonl_path, 'w') as f: 119 | for task in tasks_discover: 120 | f.write(json.dumps(task) + '\n') 121 | 122 | print(f"Step 4 complete: Discovery tasks saved to {tasks_discover_jsonl_path}") 123 | ``` 124 | 125 | --- 126 | 127 | ## Step 5: Load Discovery Tasks from JSONL (Optional) 128 | 129 | You can reload the tasks from JSONL at any time. This is helpful if you are performing the discovery in a separate environment. 130 | 131 | ### Load Discovery Tasks from JSONL (Optional) 132 | 133 | Demonstrates how to retrieve tasks from the JSONL file we just saved. 134 | 135 | ```python 136 | loaded_discovery_tasks = [] 137 | with open(tasks_discover_jsonl_path, 'r') as f: 138 | for line in f: 139 | loaded_discovery_tasks.append(json.loads(line)) 140 | 141 | print("Step 5 complete: Discovery tasks reloaded from JSONL.") 142 | print("A sample reloaded discovery task:", loaded_discovery_tasks[0]) 143 | ``` 144 | 145 | --- 146 | 147 | ## Step 6: Run Label Discovery 148 | 149 | We now run these tasks through the discovery skill, which should suggest a set of labels or topics. 150 | 151 | ### Run Label Discovery 152 | 153 | We request the skill to analyze all tasks, returning any discovered labels. Typically, the output is contained in a single record (often keyed by "0") with a "labels" field. 154 | 155 | ```python 156 | discovery_output = discover_skill.run_tasks_in_parallel(loaded_discovery_tasks) 157 | discovered_labels = discovery_output.get("0", {}).get("labels", []) 158 | 159 | print("Step 6 complete: Labels discovered.") 160 | print("Discovered labels:", discovered_labels) 161 | ``` 162 | 163 | --- 164 | 165 | ## Step 7: Define the Classification Skill using Discovered Labels 166 | 167 | Once we have a set of labels, we can assign them to individual data entries using ClassificationSkill. 168 | 169 | ### Classification Skill with Discovered Labels 170 | 171 | We initialize a “ClassificationSkill” using the discovered labels, which the skill will then apply to new or existing text records. 172 | 173 | ```python 174 | def get_classification_skill(labels): 175 | client = OpenAI() 176 | classify_skill = ClassificationSkill( 177 | model_name="gpt-4o-mini", 178 | client=client, 179 | categories=labels 180 | ) 181 | return classify_skill 182 | 183 | classify_skill = get_classification_skill(discovered_labels) 184 | print("Step 7 complete: Classification skill initialized with discovered labels.") 185 | ``` 186 | 187 | --- 188 | 189 | ## Step 8: Create Classification Tasks 190 | 191 | We now form classification tasks for each text review, letting the skill know where to find the textual content. 192 | 193 | ### Create Tasks for Classification 194 | 195 | Here, we transform our text data into classification tasks, telling the skill which column to treat as text. 196 | 197 | ```python 198 | tasks_classify = classify_skill.create_tasks( 199 | text_reviews, 200 | column_modalities={"comment": "text"} 201 | ) 202 | 203 | print("Step 8 complete: Classification tasks created.") 204 | print("Sample classification task:", tasks_classify[0]) 205 | ``` 206 | 207 | --- 208 | 209 | ## Step 9: Run the Classification 210 | 211 | Next, we pass the classification tasks to the skill, retrieving a dictionary keyed by task IDs with the assigned category or categories. 212 | 213 | ### Classification 214 | 215 | We run the tasks in parallel (if supported) or sequentially, then inspect the classification results. 216 | 217 | ```python 218 | classification_results = classify_skill.run_tasks_in_parallel(tasks_classify) 219 | 220 | print("Step 9 complete: Classification finished.") 221 | print("Classification results (first few):") 222 | for task_id, cats in list(classification_results.items())[:3]: 223 | print(f" Task ID {task_id}: {cats}") 224 | ``` 225 | 226 | --- 227 | 228 | ## Step 10: Map & Store the Final Labeled Data 229 | 230 | Finally, we attach each classification result to the original text data, storing the outcome in a JSONL file for future analysis. 231 | 232 | ### Map and Store Results 233 | 234 | We map each classification result to its corresponding data record and (optionally) write the annotated data to a JSONL file for safe-keeping and further analysis. 235 | 236 | ```python 237 | # Map the classification results back to the original data 238 | annotated_data = [] 239 | for task_id_str, output_json in classification_results.items(): 240 | task_id = int(task_id_str) 241 | record = text_reviews[task_id] 242 | record["classification"] = output_json # attach the classification result 243 | annotated_data.append(record) 244 | 245 | # Print a sample annotated record 246 | print("Sample annotated record:", annotated_data[0]) 247 | 248 | # (Optional) Save annotated data to JSONL 249 | final_results_path = os.path.join(json_folder, "classification_results.jsonl") 250 | with open(final_results_path, 'w') as f: 251 | for rec in annotated_data: 252 | f.write(json.dumps(rec) + '\n') 253 | 254 | print(f"Step 10 complete: Annotated results saved to {final_results_path}") 255 | ``` 256 | 257 | --- 258 | 259 | ## Summary and Next Steps 260 | 261 | Using these steps, you have: 262 | 263 | - Text data prepared in a list of dictionaries. 264 | - A label discovery skill that aggregates topics across your dataset. 265 | - A classification skill that assigns those discovered labels to each record. 266 | - Task creation, storability (JSONL), and reloadability to maintain transparent, reproducible workflows. 267 | - Final annotated data, also in JSONL, for deeper analytics or integration with other systems. 268 | 269 | From here, you can refine or expand: 270 | 271 | - Use more complex instructions for label discovery. 272 | - Develop additional skills (e.g., sentiment analysis, summarization). 273 | - Integrate discovered/classified labels into your data pipeline or dashboards. 274 | 275 | This completes our text-only guide for discovering and classifying labels using flashlearn! 276 | -------------------------------------------------------------------------------- /examples/Product intelligence/user_behaviour_analysis.md: -------------------------------------------------------------------------------- 1 | # User Behavior Workflow 2 | In this tutorial, we'll build an end-to-end AI workflow that: 3 | 1. Analyzes user behavior (e.g., detecting churn risk, identifying power users). 4 | 2. Automates task management based on insights (e.g., assigning tasks or follow-ups). 5 | 3. Generates clear, actionable insights for business or application decisions. 6 | 7 | We will: 8 | - Generate synthetic data about user interactions. 9 | - Learn a "Skill" (using the flashlearn approach). 10 | - Store, load, and run tasks. 11 | - Finally, map and store the results for further analysis or automation. 12 | 13 | This ensures reproducibility and easy sharing or re-use of the AI logic. 14 | 15 | ```python 16 | # Step 0: Imports and Environment Setup 17 | 18 | import os 19 | import json 20 | import random 21 | from flashlearn.skills.learn_skill import LearnSkill 22 | from flashlearn.skills import GeneralSkill 23 | 24 | # (Optional) If you have OpenAI or other providers requiring an API key: 25 | # os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY_HERE" 26 | 27 | # Directory to store JSON artifacts (skills, tasks, results) 28 | json_folder = "json_artifacts" 29 | os.makedirs(json_folder, exist_ok=True) 30 | 31 | print("Environment setup complete.") 32 | ``` 33 | 34 | ## Step 1: Synthetic User Behavior Data 35 | 36 | First, we'll create some synthetic data that captures user interactions. This data might represent daily usage statistics, clicks, purchases, or anything relevant to your scenario. In our example, each user will have: 37 | - a unique user ID 38 | - a daily usage frequency 39 | - a “churn risk factor” (a random score simulating likelihood of inactivity) 40 | - an activity log or textual summary 41 | 42 | We'll produce 50 synthetic records in a simple Python list where each element is a dictionary. 43 | 44 | ```python 45 | # Step 1: Generate Synthetic User Behavior Data 46 | 47 | def generate_synthetic_user_data(num_records=50): 48 | data = [] 49 | for i in range(num_records): 50 | usage = random.randint(0, 100) 51 | churn_risk = random.uniform(0, 1) # 0 to 1, higher means more at-risk 52 | data.append({ 53 | "user_id": f"user_{i}", 54 | "daily_usage": usage, 55 | "churn_risk_score": round(churn_risk, 2), 56 | "activity_summary": ( 57 | "User frequently checks notifications, " if usage > 50 else "User occasionally logs in, " 58 | ) + f"churn risk {churn_risk:.2f}" 59 | }) 60 | return data 61 | 62 | # Create and inspect a small sample 63 | user_data = generate_synthetic_user_data(num_records=50) 64 | print(json.dumps(user_data[:3], indent=2)) 65 | ``` 66 | 67 | ## Step 2: Learn a Skill for Analyzing User Behavior 68 | 69 | We will use the flashlearn "LearnSkill" approach to create a skill that classifies users into different segments (e.g., "high risk", "moderate risk", "low risk", "power user", etc.). The instructions for the skill will ask the model to do two things: 70 | 1. Classify the user into one of a few categories based on their daily usage and churn risk. 71 | 2. Provide a short textual insight or recommendation. 72 | 73 | This is just an example: in a real scenario, you can refine the prompt or instructions to best match your data and goals. 74 | 75 | ```python 76 | # Step 2: Learn the Skill 77 | 78 | learner = LearnSkill( 79 | model_name="gpt-4o-mini", # Example model name 80 | verbose=True 81 | ) 82 | 83 | analysis_instruction = ( 84 | "Given a user's daily_usage and churn_risk_score, " 85 | "assign a 'user_segment' under the keys: high_risk, moderate_risk, low_risk, or power_user. " 86 | "Additionally, provide a short insight under the key 'recommendation'. " 87 | "Output JSON with exactly these keys: user_segment, recommendation." 88 | ) 89 | 90 | learned_skill = learner.learn_skill( 91 | user_data, 92 | task=analysis_instruction, 93 | model_name="gpt-4o-mini" # Use the same or different model 94 | ) 95 | 96 | print("Skill learned.") 97 | ``` 98 | 99 | ## Step 3: Store the Learned Skill as JSON 100 | 101 | Once the skill is learned, we can serialize its definition to a JSON file so we can reuse it without re-learning. This is beneficial for offline, production pipelines, or sharing with your team. 102 | 103 | ```python 104 | # Step 3: Store the Learned Skill 105 | 106 | skill_path = os.path.join(json_folder, "user_behavior_analysis_skill.json") 107 | learned_skill.save(skill_path) 108 | print(f"Skill saved to {skill_path}") 109 | ``` 110 | 111 | ## Step 4: Load the Skill from JSON 112 | 113 | To demonstrate reusability, let's load the skill from the JSON file we just saved. Flashlearn provides a simple load_skill() method on GeneralSkill. 114 | 115 | ```python 116 | # Step 4: Load the Skill 117 | skill_path = os.path.join(json_folder, "user_behavior_analysis_skill.json") 118 | with open(skill_path, "r", encoding="utf-8") as file: 119 | definition = json.load(file) 120 | loaded_skill = GeneralSkill.load_skill(definition) 121 | print("Skill loaded from JSON:", loaded_skill) 122 | ``` 123 | 124 | ## Step 5: Create Tasks 125 | 126 | We now transform each item in our user_data list into a JSON-based "task" that can be processed by our loaded skill. These tasks often contain the user data and a minimal set of instructions or context. 127 | 128 | ```python 129 | # Step 5: Create Tasks 130 | 131 | tasks = loaded_skill.create_tasks(user_data) 132 | print("Sample Task:", json.dumps(tasks[0], indent=2)) 133 | ``` 134 | 135 | ## Step 6: Save Tasks as JSONL 136 | 137 | We can store these tasks in a JSONL file so that we can inspect or process them offline, or in a different environment (for example, on a machine without direct access to raw data). 138 | 139 | ```python 140 | # Step 6: Save tasks to a JSONL file 141 | 142 | tasks_jsonl_path = os.path.join(json_folder, "user_behavior_tasks.jsonl") 143 | with open(tasks_jsonl_path, 'w') as f: 144 | for task in tasks: 145 | f.write(json.dumps(task) + '\n') 146 | 147 | print(f"Tasks saved to {tasks_jsonl_path}") 148 | ``` 149 | 150 | ## Step 7: Load Tasks from JSONL 151 | 152 | When you are ready to process the tasks (e.g., in a separate workflow or after verification), reload them from JSONL. 153 | 154 | ```python 155 | # Step 7: Load tasks from the tasks JSONL file 156 | 157 | loaded_tasks = [] 158 | with open(tasks_jsonl_path, 'r') as f: 159 | for line in f: 160 | loaded_tasks.append(json.loads(line)) 161 | 162 | print(f"Loaded {len(loaded_tasks)} tasks from {tasks_jsonl_path}") 163 | print("Example loaded task:", loaded_tasks[0]) 164 | ``` 165 | 166 | ## Step 8: Process Tasks (Analysis) 167 | 168 | Next, we'll use our skill to process the tasks. The skill will classify users based on their data and provide a short recommendation. 169 | 170 | ```python 171 | # Step 8: Run the tasks to generate results 172 | 173 | analysis_results = loaded_skill.run_tasks_in_parallel(loaded_tasks) 174 | print("Sample result (task_id, output):", list(analysis_results.items())[0]) 175 | ``` 176 | 177 | ## Step 9: Map the Results to Original Data 178 | 179 | We then attach each output (by task ID) back to the corresponding user record in our dataset. This merges the model's insights with the original data, enabling easy analysis or further automation. 180 | 181 | ```python 182 | # Step 9: Mapping results back to user_data 183 | 184 | merged_data = [] 185 | for task_id_str, output_json in analysis_results.items(): 186 | task_id = int(task_id_str) # Convert string ID back to integer 187 | user_record = user_data[task_id] 188 | user_record["analysis_result"] = output_json # e.g. {user_segment, recommendation} 189 | merged_data.append(user_record) 190 | 191 | # Preview the merged data 192 | print(json.dumps(merged_data[:2], indent=2)) 193 | ``` 194 | 195 | ## Step 10: Store the Final Results for Future Use 196 | 197 | Finally, we can store the annotated user data in a JSONL file for further analytics or feeding into other processes (like automated messaging, dashboard updates, or advanced personalization). 198 | 199 | ```python 200 | # Step 10: Store the final annotated results 201 | 202 | final_results_path = os.path.join(json_folder, "user_behavior_analysis_results.jsonl") 203 | with open(final_results_path, 'w') as f: 204 | for entry in merged_data: 205 | f.write(json.dumps(entry) + '\n') 206 | 207 | print(f"Final analysis results stored at {final_results_path}") 208 | ``` 209 | 210 | # Next Steps: Automate Task Management & Generate Actionable Insights 211 | 212 | Now that user segments and recommendations are attached to each record, we can further automate task management. For instance: 213 | 214 | - Automatically assign a "Retention Task" for users labeled `"high_risk"`. 215 | - Send "Thank you" messages or next-level features to `"power_user"` segments. 216 | - Re-engage with `"moderate_risk"` or `"low_risk"` users via marketing campaigns. 217 | 218 | In future steps, you could: 219 | 1. Create a second skill that takes these analyses and decides internal tasks to be assigned to your team (e.g., "contact user to see if they need assistance"). 220 | 2. Store and load that skill in the same manner (JSON-based). 221 | 3. Generate a new set of tasks for an internal Task Management system (e.g., tickets, follow-ups). 222 | 4. Run them, gather results, and feed them back into your overall system or CRM. 223 | 224 | This tutorial demonstrates the modular nature of AI-driven workflows: each skill can be learned, stored, loaded, and run on your data pipeline, with concise JSON artifacts to ensure maintainability and reproducibility. 225 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # FlashLearn – Examples Directory 2 | 3 | Below you’ll find an overview of each file, what it showcases, and links to the actual code. You can run these examples (assuming you’ve installed FlashLearn and set your “OPENAI_API_KEY”) simply by using: 4 | 5 | --- 6 | 7 | ## How to Run 8 | 9 | 1. Clone or download the FlashLearn repository (or just copy the examples folder). 10 | 2. Install FlashLearn: 11 | ```bash 12 | pip install flashlearn 13 | ``` 14 | 3. Set your “OPENAI_API_KEY” (or other provider keys) in your environment: 15 | ```bash 16 | export OPENAI_API_KEY="YOUR_API_KEY" 17 | ``` 18 | 4. Navigate to the “examples” folder and pick any script. Then run, for example: 19 | ```bash 20 | python sentiment_classification.py 21 | ``` 22 | 5. Check the console output and JSON results files (e.g., “results.jsonl”). 23 | -------------------------------------------------------------------------------- /examples/Sales/personalized_emails.md: -------------------------------------------------------------------------------- 1 | # Generating Personalized Emails for Accounting AI Agent 2 | 3 | 4 | Need to send targeted emails that highlight how an AI can streamline accounting tasks? In this guide, we’ll walk through creating a personalized email generator that references each lead’s company, role, interests, and last contact. 5 | 6 | --- 7 | 8 | ## Step 0: Imports and Environment Setup 9 | 10 | We begin by importing the necessary libraries, setting environment variables (e.g., API keys), and creating a folder to store JSON artifacts (skills, tasks, final outputs). 11 | 12 | ### Environment Setup 13 | 14 | We import libraries, set our API keys (if needed), and ensure we have a folder for storing artifacts (skills, tasks, JSONL results). 15 | 16 | ```python 17 | import os 18 | import json 19 | 20 | # (Optional) If you're using OpenAI or another provider: 21 | os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY" 22 | 23 | # Folder to store JSON artifacts 24 | json_folder = "json_artifacts" 25 | os.makedirs(json_folder, exist_ok=True) 26 | 27 | print("Step 0 complete: Environment setup.") 28 | ``` 29 | 30 | --- 31 | 32 | ## Step 1: Prepare Example User/Lead Data 33 | 34 | Each record in our dataset represents a prospective client or lead. We include fields such as their name, company, role, interest area, and a short mention of how we last contacted them. This will be used to generate personalized emails pitching our “vertical AI agent for Accounting.” 35 | 36 | ### Prepare Example Data 37 | 38 | We define a small dataset of prospects that might be interested in our AI accounting tool. Each record includes relevant fields the AI will use to customize the email content. 39 | 40 | ```python 41 | import random 42 | from datetime import datetime, timedelta 43 | 44 | def generate_fake_leads(num_leads=5): 45 | roles = ["Finance Manager", "Chief Accountant", "Accounting Clerk", "CFO"] 46 | interests = ["Tax Compliance", "Bookkeeping Automation", "Auditing Support", "Invoice Management"] 47 | 48 | data = [] 49 | base_date = datetime.now() 50 | 51 | for i in range(num_leads): 52 | # Synthetic name and company 53 | name = f"LeadName_{i}" 54 | company = f"Company_{i}" 55 | 56 | # Random role, interest, and last_contact 57 | role = random.choice(roles) 58 | interest = random.choice(interests) 59 | days_ago = random.randint(1, 30) 60 | last_contact_date = (base_date - timedelta(days=days_ago)).strftime("%Y-%m-%d") 61 | 62 | data.append({ 63 | "name": name, 64 | "company": company, 65 | "role": role, 66 | "interest_topic": interest, 67 | "last_contact_date": last_contact_date 68 | }) 69 | 70 | return data 71 | 72 | lead_data = generate_fake_leads(5) # generate 5 leads 73 | 74 | print("Step 1 complete: Example lead data generated.") 75 | print("Sample lead record:", lead_data[0]) 76 | ``` 77 | 78 | --- 79 | 80 | ## Step 2: Define the Skill for Personalizing Emails 81 | 82 | We create and train a skill (using flashlearn, for example), instructing it to generate a personalized email referencing the user’s data and pitching our vertical AI agent for Accounting. The skill will produce a JSON output containing a suggested “subject” and “body” for the email. 83 | 84 | ### Define an AI Skill for Personalized Emails 85 | 86 | We use “LearnSkill” from flashlearn to create a skill that transforms user/lead data into a customized email promoting a vertical AI solution for accounting. 87 | 88 | ```python 89 | from flashlearn.skills.learn_skill import LearnSkill 90 | from flashlearn.skills import GeneralSkill 91 | 92 | def create_email_skill(): 93 | learner = LearnSkill( 94 | model_name="gpt-4o-mini", # Example model name 95 | verbose=True 96 | ) 97 | 98 | # Instruction for email generation 99 | email_instruction = ( 100 | "You are an AI writing outreach emails. The context is a vertical AI agent for Accounting. " 101 | "For each lead, use the fields (name, company, role, interest_topic, last_contact_date) to create a short, personalized email. " 102 | "Return JSON with two keys: 'subject' and 'body'. The subject should reference the lead's role/company, " 103 | "and the body should mention our AI agent for Accounting, referencing any relevant interest_topic. " 104 | "Keep it friendly, concise, and professional." 105 | ) 106 | 107 | # Learn the skill using the lead data and the above instruction 108 | skill = learner.learn_skill( 109 | lead_data, 110 | task=email_instruction, 111 | model_name="gpt-4o-mini" 112 | ) 113 | 114 | return skill 115 | 116 | email_skill = create_email_skill() 117 | print("Step 2 complete: Email skill created and learned.") 118 | ``` 119 | 120 | --- 121 | 122 | ## Step 3: Store the Learned Skill as JSON 123 | 124 | We serialize (save) the newly created email skill so that we can reload it at any time, avoiding the need to re-learn the instructions and parameters each run. 125 | 126 | ### Store the Learned Skill in JSON 127 | 128 | This ensures we can reuse the same skill definition in the future for consistent email generation. 129 | 130 | ```python 131 | email_skill_path = os.path.join(json_folder, "accounting_email_skill.json") 132 | email_skill.save(email_skill_path) 133 | print(f"Step 3 complete: Skill saved to {email_skill_path}") 134 | ``` 135 | 136 | --- 137 | 138 | ## Step 4: Load the Saved Skill 139 | 140 | When we want to generate emails in a production or separate environment, we simply load the stored skill from JSON, rather than re-learning it. 141 | 142 | ### Load the Saved Skill 143 | 144 | We restore the skill from JSON and ensure it’s ready to generate personalized emails. 145 | 146 | ```python 147 | email_skill_path = os.path.join(json_folder, "accounting_email_skill.json") 148 | with open(email_skill_path, "r", encoding="utf-8") as file: 149 | definition = json.load(file) 150 | loaded_email_skill = GeneralSkill.load_skill(definition) 151 | print("Step 4 complete: Skill loaded from JSON:", loaded_email_skill) 152 | ``` 153 | 154 | --- 155 | 156 | ## Step 5: Create Tasks for Email Generation 157 | 158 | We transform each lead record into a task that the skill can process. In this example, each field relevant to personalization is simply treated as text or numeric data. 159 | 160 | ### Create Tasks for Personalized Emails 161 | 162 | Each lead is converted into a flashlearn “task.” Label columns if they’re purely textual (e.g., name, company, role). 163 | 164 | ```python 165 | column_modalities = { 166 | "name": "text", 167 | "company": "text", 168 | "role": "text", 169 | "interest_topic": "text", 170 | "last_contact_date": "text" 171 | } 172 | 173 | email_tasks = loaded_email_skill.create_tasks( 174 | lead_data, 175 | column_modalities=column_modalities 176 | ) 177 | 178 | print("Step 5 complete: Email tasks created.") 179 | print("Sample email task:", email_tasks[0]) 180 | ``` 181 | 182 | --- 183 | 184 | ## Step 6: (Optional) Save Email Tasks to JSONL 185 | 186 | We can save the tasks in a JSONL file, which is handy for auditing or offline processing. 187 | 188 | ### Save Email Tasks to JSONL (Optional) 189 | 190 | This preserves the exact tasks so they can be processed later or in another environment. 191 | 192 | ```python 193 | email_tasks_path = os.path.join(json_folder, "email_tasks.jsonl") 194 | with open(email_tasks_path, 'w') as f: 195 | for task in email_tasks: 196 | f.write(json.dumps(task) + '\n') 197 | 198 | print(f"Step 6 complete: Email tasks saved to {email_tasks_path}") 199 | ``` 200 | 201 | --- 202 | 203 | ## Step 7: Load Email Tasks from JSONL (Optional) 204 | 205 | If we want to separate the task creation from the actual email generation, we can load the tasks from our JSONL file. 206 | 207 | ### Load Email Tasks from JSONL (Optional) 208 | 209 | Demonstrates offline or separate environment usage by reloading tasks. 210 | 211 | ```python 212 | reloaded_email_tasks = [] 213 | with open(email_tasks_path, 'r') as f: 214 | for line in f: 215 | reloaded_email_tasks.append(json.loads(line)) 216 | 217 | print("Step 7 complete: Email tasks reloaded.") 218 | print("Example reloaded task:", reloaded_email_tasks[0]) 219 | ``` 220 | 221 | --- 222 | 223 | ## Step 8: Run the Personalized Email Generation 224 | 225 | Now we let our skill process all the tasks in parallel (where supported). The AI will create a subject and body for each user, referencing the user’s name, company, last contact, and role. 226 | 227 | ### Generate Personalized Emails 228 | 229 | The skill outputs a JSON object with keys “subject” and “body” for each lead, containing a tailored email. 230 | 231 | ```python 232 | email_results = loaded_email_skill.run_tasks_in_parallel(reloaded_email_tasks) 233 | 234 | print("Step 8 complete: Personalized emails generated.") 235 | print("Sample result (task_id, output):") 236 | for task_id, email_data in list(email_results.items())[:1]: 237 | print(f" Task ID {task_id}: {email_data}") 238 | ``` 239 | 240 | --- 241 | 242 | ## Step 9: Map the Generated Emails Back to the Lead Data 243 | 244 | We attach the generated email (subject and body) to each original lead record, preserving the context for future steps or direct sending. 245 | 246 | ### Integrate Email Output with Original Lead Data 247 | 248 | We align each AI-generated email with the matching lead, creating a final annotated dataset. 249 | 250 | ```python 251 | annotated_leads = [] 252 | for task_id_str, output_json in email_results.items(): 253 | task_id = int(task_id_str) 254 | lead_record = lead_data[task_id] 255 | lead_record["generated_email"] = output_json 256 | annotated_leads.append(lead_record) 257 | 258 | print("Step 9 complete: Email output mapped back to lead data.") 259 | print("Sample annotated lead:", annotated_leads[0]) 260 | ``` 261 | 262 | --- 263 | 264 | ## Step 10: Store Final Annotated Results 265 | 266 | Finally, we can write these annotated lead records (which now contain both original data and a newly generated email) to a JSONL file for further review or integration into a sending system (like an email marketing tool). 267 | 268 | ### Store the Final Generated Emails 269 | 270 | We save the annotated leads to a JSONL file. Each record now includes the personal email subject and body. 271 | 272 | ```python 273 | final_emails_path = os.path.join(json_folder, "personalized_emails.jsonl") 274 | with open(final_emails_path, 'w') as f: 275 | for lead in annotated_leads: 276 | f.write(json.dumps(lead) + '\n') 277 | 278 | print(f"Step 10 complete: Final personalized emails saved to {final_emails_path}") 279 | ``` 280 | 281 | --- 282 | 283 | ## Next Steps 284 | Next steps might involve: 285 | - Integrating with an actual email marketing service or CRM. 286 | - Refining prompts or including example-based instruction to improve email quality or style. 287 | - Segmenting leads by interest or role, then generating different email variations. 288 | -------------------------------------------------------------------------------- /examples/Sales/sentiment_classification.md: -------------------------------------------------------------------------------- 1 | # Sentiment Classification 2 | ## Pro tip: Ctrl + C -> ChatGPT -> Ctrl + V -> Describe your problem-> Get your code 3 | --- 4 | 5 | ## Step 0: Imports and Environment Setup 6 | 7 | We begin by importing the necessary libraries, setting environment variables (if needed), and preparing any directories for storing JSON artifacts (e.g., skill files, tasks, and final results). 8 | 9 | ### Environment Setup 10 | 11 | Import libraries, set API keys if necessary, and optionally create folders for storing JSON artifacts. 12 | 13 | ```python 14 | import json 15 | import os 16 | 17 | from openai import OpenAI 18 | from flashlearn.skills.classification import ClassificationSkill 19 | from flashlearn.utils import imdb_reviews_50k 20 | 21 | # (Optional) Ensure your API key is set if you're using OpenAI as a provider 22 | # os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY" 23 | 24 | # Create a folder for JSON artifacts if desired 25 | json_folder = "json_artifacts" 26 | os.makedirs(json_folder, exist_ok=True) 27 | 28 | print("Step 0 complete: Environment setup.") 29 | ``` 30 | 31 | --- 32 | 33 | ## Step 1: Setup Your Provider 34 | 35 | We configure our client, which in this case is openai.OpenAI. You can replace the keys or base URL if you’re using a different deployment or custom endpoint. 36 | 37 | ### Setup Your Provider 38 | 39 | Here, we prepare the AI provider (e.g., OpenAI) for classification tasks. 40 | 41 | ```python 42 | def get_provider(): 43 | # If you have a custom base_url or API key, set it here 44 | # deep_seek = OpenAI( 45 | # api_key='YOUR DEEPSEEK API KEY', 46 | # base_url="https://api.deepseek.com", 47 | # ) 48 | client = OpenAI() 49 | return client 50 | 51 | print("Step 1 complete: Provider ready.") 52 | ``` 53 | 54 | --- 55 | 56 | ## Step 2: Load Sample Data 57 | 58 | We use `flashlearn.utils.imdb_reviews_50k` to load a sample of IMDB movie reviews. Each item in the returned list is a dictionary containing, typically, "review" (the text) and "sentiment" (the true label). 59 | 60 | ### Load Sample Data 61 | 62 | We pull 100 IMDB reviews for demonstration. Each record has a "review" string and "sentiment" label. 63 | 64 | ```python 65 | client = get_provider() 66 | 67 | # Load 100 reviews from IMDB 68 | reviews = imdb_reviews_50k(sample=100) 69 | print(f"Step 2 complete: Loaded {len(reviews)} sample reviews.") 70 | print("Sample review:", reviews[0]) 71 | ``` 72 | 73 | --- 74 | 75 | ## Step 3: Initialize the Classification Skill 76 | 77 | We create a `ClassificationSkill`, specifying model parameters, the fixed categories (“positive” and “negative”), and a system prompt clarifying the classification instructions. 78 | 79 | ### Initialize the Classification Skill 80 | 81 | We configure the target categories, maximum number of categories (1 in this case), and supply a prompt describing the desired sentiment classification. 82 | 83 | ```python 84 | skill = ClassificationSkill( 85 | model_name="gpt-4o-mini", # or "deepseek-chat", etc. 86 | client=client, 87 | categories=["positive", "negative"], 88 | max_categories=1, 89 | system_prompt="We want to classify short movie reviews by sentiment." 90 | ) 91 | 92 | print("Step 3 complete: Classification skill initialized.") 93 | ``` 94 | 95 | --- 96 | 97 | ## Step 4: Prepare Classification Tasks 98 | 99 | We remove the “sentiment” label for classification to replicate a real inference scenario. We then transform this data into tasks recognized by the skill. Finally, we store the tasks in a JSONL file for auditing or offline processing. 100 | 101 | ### Prepare Classification Tasks 102 | 103 | In this step, we remove the ground truth sentiment from each review, generate tasks, and save them in a JSONL file. 104 | 105 | ```python 106 | # Remove the sentiment to simulate unlabeled data 107 | removed_sentiment = [{"review": x["review"]} for x in reviews] 108 | 109 | # Create tasks 110 | tasks = skill.create_tasks(removed_sentiment) 111 | 112 | # Save tasks to JSONL for reproducibility 113 | tasks_jsonl_path = os.path.join(json_folder, "tasks.jsonl") 114 | with open(tasks_jsonl_path, 'w') as jsonl_file: 115 | for entry in tasks: 116 | jsonl_file.write(json.dumps(entry) + '\n') 117 | 118 | print(f"Step 4 complete: Classification tasks created and saved to {tasks_jsonl_path}") 119 | ``` 120 | 121 | --- 122 | 123 | ## Step 5: Execute Classification Tasks 124 | 125 | We pass the tasks to the skill, which infers the sentiment for each review. The function `run_tasks_in_parallel` processes them either concurrently or sequentially behind the scenes. 126 | 127 | ### Execute Classification Tasks 128 | 129 | We run our tasks to produce sentiment predictions for each review. 130 | 131 | ```python 132 | results = skill.run_tasks_in_parallel(tasks) 133 | print("Step 5 complete: Classification tasks executed.") 134 | print("Sample classification result:", list(results.items())[0]) 135 | ``` 136 | 137 | --- 138 | 139 | ## Step 6: Map Results and Check Accuracy 140 | 141 | We attach each predicted category to the original dataset and compare it against the known “sentiment” label to compute accuracy. 142 | 143 | ### Map Results and Check Accuracy 144 | 145 | We add the model’s predicted category to each review record, then compute the fraction of correct classifications. 146 | 147 | ```python 148 | correct = 0 149 | for i, review in enumerate(reviews): 150 | predicted_category = results[str(i)]["categories"] 151 | reviews[i]["category"] = predicted_category 152 | if review["sentiment"] == predicted_category: 153 | correct += 1 154 | 155 | accuracy = round(correct / len(reviews), 2) 156 | print(f"Step 6 complete: Accuracy = {accuracy * 100}%") 157 | ``` 158 | 159 | --- 160 | 161 | ## Step 7: Save Final Results to a JSONL File 162 | 163 | Finally, we store the updated reviews (including predicted labels) to a JSONL file, preserving both the original data and our classification outputs. 164 | 165 | ### Save Final Results 166 | 167 | We write the annotated (predicted) reviews to another JSONL, making it easy to review or feed into subsequent processes. 168 | 169 | ```python 170 | results_jsonl_path = os.path.join(json_folder, "results.jsonl") 171 | with open(results_jsonl_path, 'w') as jsonl_file: 172 | for entry in reviews: 173 | jsonl_file.write(json.dumps(entry) + '\n') 174 | 175 | print(f"Step 7 complete: Final results saved to {results_jsonl_path}") 176 | ``` 177 | 178 | --- 179 | 180 | ## Step 8: Save the Skill Configuration 181 | 182 | We serialize the `ClassificationSkill` definition for future re-use. This allows you to load and re-run the exact same classification logic without re-initializing or re-configuring. 183 | 184 | ### Save the Skill Configuration 185 | 186 | We store the skill as a JSON file to ensure reproducibility and allow offline usage or sharing. 187 | 188 | ```python 189 | skill.save("BinaryClassificationSkill.json") 190 | print("Step 8 complete: Skill configuration saved to BinaryClassificationSkill.json") 191 | ``` 192 | 193 | --- 194 | 195 | ## Full Code 196 | 197 | Below is the full, consolidated Python script for reference: 198 | 199 | ```python 200 | import json 201 | import os 202 | 203 | from openai import OpenAI 204 | from flashlearn.skills.classification import ClassificationSkill 205 | from flashlearn.utils import imdb_reviews_50k 206 | 207 | def main(): 208 | # Step 1: Setup your provider 209 | # os.environ["OPENAI_API_KEY"] = 'YOUR_API_KEY' 210 | client = OpenAI() 211 | 212 | # Step 2: Load sample data 213 | reviews = imdb_reviews_50k(sample=100) 214 | 215 | # Step 3: Initialize the Classification Skill 216 | skill = ClassificationSkill( 217 | model_name="gpt-4o-mini", 218 | client=client, 219 | categories=["positive", "negative"], 220 | max_categories=1, 221 | system_prompt="We want to classify short movie reviews by sentiment." 222 | ) 223 | 224 | # Step 4: Prepare classification tasks 225 | removed_sentiment = [{'review': x['review']} for x in reviews] 226 | tasks = skill.create_tasks(removed_sentiment) 227 | with open('tasks.jsonl', 'w') as jsonl_file: 228 | for entry in tasks: 229 | jsonl_file.write(json.dumps(entry) + '\n') 230 | 231 | # Step 5: Execute classification tasks 232 | results = skill.run_tasks_in_parallel(tasks) 233 | 234 | # Step 6: Map results and check accuracy 235 | correct = 0 236 | for i, review in enumerate(reviews): 237 | predicted_category = results[str(i)]['categories'] 238 | reviews[i]['category'] = predicted_category 239 | if reviews[i]['sentiment'] == predicted_category: 240 | correct += 1 241 | print(f'Accuracy: {round(correct / len(reviews), 2) * 100}%') 242 | 243 | # Step 7: Save results to a JSONL file 244 | with open('results.jsonl', 'w') as jsonl_file: 245 | for entry in reviews: 246 | jsonl_file.write(json.dumps(entry) + '\n') 247 | 248 | # Step 8: Save the Skill configuration 249 | skill.save("BinaryClassificationSkill.json") 250 | 251 | if __name__ == "__main__": 252 | main() 253 | ``` 254 | 255 | --- 256 | 257 | ## Summary and Next Steps 258 | 259 | By following this tutorial, you have: 260 | 1. Configured an OpenAI-like client. 261 | 2. Loaded IMDb movie reviews. 262 | 3. Created a sentiment classification skill. 263 | 4. Generated classification tasks and stored them as JSONL. 264 | 5. Executed classification, checked accuracy, and stored final results. 265 | 6. Saved the skill configuration for future re-use. 266 | 267 | You can adapt or extend this approach by: 268 | - Incorporating additional sentiment categories (like “neutral”). 269 | - Creating downstream tasks based on the predicted sentiment (e.g., filtering content, triggering alerts). 270 | 271 | This completes our step-by-step guide for classifying IMDb reviews using Flashlearn’s ClassificationSkill! 272 | -------------------------------------------------------------------------------- /examples/browser_use_price_matching/README.md: -------------------------------------------------------------------------------- 1 | # Browser Price Matching Tool 2 | 3 | This project helps you adjust your product prices by comparing them to similar products available in the market. It uses a browser agent to search for products (e.g., on Amazon) and leverages custom "skills" to generate effective search queries and structure web data into a standard format. The enriched data can then be used to update your product prices. 4 | 5 | --- 6 | 7 | ## File Structure 8 | 9 | - **learn_skill.py** 10 | Learns a new skill for creating clear and effective product search queries using the FlashLearn library and OpenAI’s GPT-4o-mini model. 11 | 12 | - **learn_skill_select_best_product.py** 13 | Learns a skill to parse browser response text and select the best matching product along with its key details (name, price, short description). 14 | 15 | - **make_query.json** 16 | Contains the generated skill definition for producing search queries from product names. This file is produced by running `learn_skill.py`. 17 | 18 | - **select_product.json** 19 | Contains the generated skill definition for parsing search results and selecting the best product. This file is produced by running `learn_skill_select_best_product.py`. 20 | 21 | - **product_price_matching.py** 22 | The main pipeline that: 23 | - Reads product data (from CSV or demo list). 24 | - Uses the skill in `make_query.json` to generate clean search queries. 25 | - Launches asynchronous browser agents to search on Amazon. 26 | - Structures the scraped web data. 27 | - Uses the skill in `select_product.json` to extract the best matching product details. 28 | - Enriches and prints the final product data. 29 | 30 | --- 31 | 32 | ## Installation & Setup 33 | 34 | ### Requirements 35 | 36 | - Python 3.7 or later 37 | - Required Python packages: 38 | - `python-dotenv` 39 | - `openai` 40 | - `langchain_openai` 41 | - `flashlearn` 42 | - `requests` 43 | - `pytest-playwright` 44 | 45 | ### Steps 46 | 47 | 1. **Install Python dependencies:** 48 | 49 | ```bash 50 | pip install python-dotenv openai langchain_openai flashlearn requests pytest-playwright 51 | ``` 52 | 53 | 2. **Install the required browsers for Playwright:** 54 | 55 | ```bash 56 | playwright install 57 | ``` 58 | 59 | 3. **Set up your OpenAI API Key:** 60 | 61 | Create a `.env` file in the project directory and add the following line: 62 | 63 | ```env 64 | OPENAI_API_KEY="sk-your_api_key_here" 65 | ``` 66 | 67 | --- 68 | 69 | ## Process Overview 70 | 71 | 1. **Learn Skill for Query Creation:** 72 | 73 | - Run `learn_skill.py`. 74 | - This script trains a skill that generates clean, Google-like search queries from your product names. 75 | - The resulting skill definition is saved as `make_query.json`. 76 | 77 | 2. **Learn Skill for Selecting the Best Product:** 78 | 79 | - Run `learn_skill_select_best_product.py`. 80 | - This script trains a skill to parse browser results and select the best matching product based on details like name, price, and a brief description. 81 | - The resulting skill definition is saved as `select_product.json`. 82 | 83 | 3. **Run the Price Matching Pipeline:** 84 | 85 | - Run `product_price_matching.py`. 86 | - The script performs the following steps: 87 | - Loads your product data (for demonstration, a list of product dictionaries is used; in production, you can modify this to load a CSV file). 88 | - Generates search queries using the `make_query.json` skill. 89 | - Starts asynchronous browser agents to search on Amazon using the generated queries. 90 | - Scrapes and structures the website data. 91 | - Uses the `select_product.json` skill to pick the best matching product. 92 | - Enriches the original product data with the new information and prints the results. 93 | 94 | --- 95 | 96 | ## Customization 97 | 98 | - **Concurrency:** 99 | Adjust the number of parallel browser agents by modifying the `concurrency` parameter in `product_price_matching.py`. 100 | 101 | - **Product Data:** 102 | Replace the demo product list with your CSV input or other data sources as needed. 103 | 104 | - **Skill Adjustments:** 105 | Feel free to extend or modify the skill definitions if your query generation or data extraction requirements change. 106 | 107 | --- 108 | 109 | ## Troubleshooting 110 | 111 | - Ensure the `.env` file is correctly set up with your valid OpenAI API key. 112 | - Verify that all required packages are installed. 113 | - Check output logs (with verbose mode enabled) for any issues during skill training or pipeline execution. 114 | - Confirm that the browsers required by Playwright are correctly installed by running `playwright install`. 115 | 116 | --- 117 | 118 | ## Conclusion 119 | 120 | The Browser Price Matching Tool automates the process of pricing analysis by: 121 | - Generating effective search queries. 122 | - Running browser agents to capture real-time product data. 123 | - Structuring and enriching your product data with relevant market information. 124 | 125 | This tool minimizes manual intervention while providing you with up-to-date market insights to help you adjust your product prices effectively. 126 | -------------------------------------------------------------------------------- /examples/browser_use_price_matching/learn_skill.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | from flashlearn.skills.learn_skill import LearnSkill 5 | from flashlearn.utils import imdb_reviews_50k 6 | load_dotenv() 7 | 8 | def main(): 9 | # Step 1: Provide your OpenAI key in .env file 10 | # Step 2: Initialize the LearnSkill 11 | learner = LearnSkill(model_name="gpt-4o-mini", verbose=True) 12 | 13 | # Step 4: Learn the skill with the defined task 14 | skill = learner.learn_skill( 15 | df=[], 16 | task='from input create one google query called on key query', 17 | model_name="gpt-4o-mini", 18 | ) 19 | 20 | # Step 5: Prepare and execute classification tasks 21 | skill.save('make_query.json') 22 | 23 | if __name__ == "__main__": 24 | main() -------------------------------------------------------------------------------- /examples/browser_use_price_matching/learn_skill_select_best_product.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from flashlearn.skills.learn_skill import LearnSkill 3 | from flashlearn.utils import imdb_reviews_50k 4 | load_dotenv() 5 | 6 | def main(): 7 | # Step 1: Provide your OpenAI 8 | 9 | # Step 2: Initialize the LearnSkill 10 | learner = LearnSkill(model_name="gpt-4o-mini", verbose=True) 11 | 12 | # Step 4: Learn the skill with the defined task 13 | skill = learner.learn_skill( 14 | df=[], 15 | task='parse text input and select the most simmilar product to the query' 16 | 'return structured prduct information for best product on key name, price (float), short_description', 17 | model_name="gpt-4o-mini", 18 | ) 19 | 20 | # Step 5: Prepare and execute classification tasks 21 | skill.save('select_product.json') 22 | 23 | if __name__ == "__main__": 24 | main() 25 | -------------------------------------------------------------------------------- /examples/browser_use_price_matching/make_query.json: -------------------------------------------------------------------------------- 1 | { 2 | "skill_class": "GeneralSkill", 3 | "system_prompt": "Exactly populate the provided function definition", 4 | "function_definition": { 5 | "type": "function", 6 | "function": { 7 | "name": "createGoogleQuery", 8 | "description": "Generates a Google search query based on the provided input.", 9 | "strict": true, 10 | "parameters": { 11 | "type": "object", 12 | "properties": { 13 | "query": { 14 | "type": "string" 15 | } 16 | }, 17 | "required": [ 18 | "query" 19 | ], 20 | "additionalProperties": false 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /examples/browser_use_price_matching/product_price_matching.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import asyncio 4 | import requests 5 | 6 | from dotenv import load_dotenv 7 | from openai import OpenAI 8 | from flashlearn.skills import GeneralSkill 9 | from browser_use.agent.service import Agent 10 | from langchain_openai import ChatOpenAI 11 | 12 | # Make .env file and OPENAI_API_KEY = "sk-proj......." 13 | load_dotenv() 14 | 15 | 16 | # ------------------------------------------------------------------------------ 17 | # Configuration 18 | # ------------------------------------------------------------------------------ 19 | MODEL_NAME = "gpt-4o-mini" 20 | 21 | 22 | # ------------------------------------------------------------------------------ 23 | # Data 24 | # ------------------------------------------------------------------------------ 25 | products = [ 26 | {"product_name": "Apple iPhone14! "}, 27 | {"product_name": " Samsung Galaxy-S23"}, 28 | {"product_name": "Sony_WH-1000XM4_Headphones"}, 29 | {"product_name": "Nintendo Switch"}, 30 | {"product_name": "Dyson! V11!! VacuumCleaner "}, 31 | {"product_name": "Dell XPS13~Laptop"}, 32 | {"product_name": "Google Nest Thermo!stat"}, 33 | {"product_name": "Tesla Model#S"}, 34 | {"product_name": " LG OLED-TV"}, 35 | {"product_name": "AirWave~Ultrasonic#Humidifier"}, 36 | {"product_name": "ZapMagic_Cleaning*Gel"}, 37 | {"product_name": "NeoBlend!!Coffee Maker"}, 38 | {"product_name": " BlueShade Sunglasses "}, 39 | {"product_name": "AquaBoost Water!!Bottle#"}, 40 | {"product_name": "PocketPro~~~Multi-tool"}, 41 | ] 42 | # For demo purposes, limit to the first 3 products 43 | products = products[:5] 44 | 45 | 46 | # ------------------------------------------------------------------------------ 47 | # Asynchronous Functions 48 | # ------------------------------------------------------------------------------ 49 | async def browser_agent(query: str) -> str: 50 | """ 51 | A single agent that goes to Amazon, searches for the given 'query', 52 | clicks on the most similar product, and returns the page text. 53 | """ 54 | agent = Agent( 55 | task=f"Go to Amazon.com, search for {query}, click on the most similar product and return page text", 56 | llm=ChatOpenAI(model="gpt-4o"), 57 | ) 58 | result = await agent.run() 59 | 60 | # Extract the text from the final item in the agent's history 61 | product_data = result.history[-1].result[0].extracted_content 62 | return product_data 63 | 64 | 65 | async def run_browser_agents_in_parallel(queries, concurrency: int = 5) -> list: 66 | """ 67 | Spawns up to 'concurrency' browser agents in parallel to handle queries. 68 | 69 | Args: 70 | queries (list[str]): A list of query strings to search on Amazon. 71 | concurrency (int): The maximum number of parallel tasks. 72 | 73 | Returns: 74 | list of str: The extracted content returned by each browser agent. 75 | """ 76 | semaphore = asyncio.Semaphore(concurrency) 77 | 78 | async def limited_browser_agent(q): 79 | async with semaphore: 80 | return await browser_agent(q["query"]) 81 | 82 | tasks = [asyncio.create_task(limited_browser_agent(q)) for q in queries] 83 | 84 | # gather results in the same order as tasks 85 | results = await asyncio.gather(*tasks) 86 | return results 87 | 88 | 89 | # ------------------------------------------------------------------------------ 90 | # Main Logic 91 | # ------------------------------------------------------------------------------ 92 | def main(): 93 | # Initialize OpenAI client 94 | client = OpenAI() 95 | 96 | # -------------------------------------------------------------------------- 97 | # Read skill definitions from JSON files 98 | # -------------------------------------------------------------------------- 99 | with open("make_query.json", "r") as file: 100 | skill_definition_query = json.load(file) 101 | 102 | with open("select_product.json", "r") as file: 103 | skill_definition_best_product = json.load(file) 104 | 105 | # -------------------------------------------------------------------------- 106 | # 1) Create queries from products 107 | # -------------------------------------------------------------------------- 108 | skill = GeneralSkill.load_skill(skill_definition_query, client=client) 109 | tasks = skill.create_tasks(products) 110 | queries_by_index = skill.run_tasks_in_parallel(tasks) 111 | # queries_by_index is typically a dict with integer keys and query dict values 112 | 113 | # -------------------------------------------------------------------------- 114 | # 2) Search on Amazon using parallel browser agents 115 | # -------------------------------------------------------------------------- 116 | # Convert the dict to a list to keep track of original indices 117 | queries_list = [queries_by_index[str(i)] for i in range(len(queries_by_index))] 118 | 119 | # Run up to N browser agents in parallel 120 | results_texts = asyncio.run(run_browser_agents_in_parallel(queries_list, concurrency=1)) 121 | 122 | # Update products with the results text 123 | for idx, text in enumerate(results_texts): 124 | products[idx].update({ 125 | "query": queries_list[idx]["query"], 126 | "results_text": str(text), 127 | }) 128 | 129 | # -------------------------------------------------------------------------- 130 | # 3) Select best match using a second skill 131 | # -------------------------------------------------------------------------- 132 | skill_best = GeneralSkill.load_skill(skill_definition_best_product, client=client) 133 | best_tasks = skill_best.create_tasks(products) 134 | best_products = skill_best.run_tasks_in_parallel(best_tasks) 135 | 136 | # Update products with the best product match 137 | for idx, best_match in best_products.items(): 138 | products[int(idx)].update({"best_product": best_match}) 139 | 140 | # -------------------------------------------------------------------------- 141 | # 4) Print final results 142 | # -------------------------------------------------------------------------- 143 | print(json.dumps(products, indent=4)) 144 | 145 | 146 | # ------------------------------------------------------------------------------ 147 | # Entry Point 148 | # ------------------------------------------------------------------------------ 149 | if __name__ == "__main__": 150 | main() -------------------------------------------------------------------------------- /examples/browser_use_price_matching/select_product.json: -------------------------------------------------------------------------------- 1 | { 2 | "skill_class": "GeneralSkill", 3 | "system_prompt": "Exactly populate the provided function definition", 4 | "function_definition": { 5 | "type": "function", 6 | "function": { 7 | "name": "FindMostSimilarProduct", 8 | "description": "Parse text input and select the most similar product to the query, returning structured product information for the best product.", 9 | "strict": true, 10 | "parameters": { 11 | "type": "object", 12 | "properties": { 13 | "name": { 14 | "type": "string" 15 | }, 16 | "price": { 17 | "type": "number" 18 | }, 19 | "short_description": { 20 | "type": "string" 21 | } 22 | }, 23 | "required": [ 24 | "name", 25 | "price", 26 | "short_description" 27 | ], 28 | "additionalProperties": false 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /examples/tests/DiscoverLabelsSkill.json: -------------------------------------------------------------------------------- 1 | { 2 | "skill_class": "DiscoverLabelsSkill", 3 | "system_prompt": "Uncover hidden themes in this movie reviews. Be funny", 4 | "function_definition": { 5 | "type": "function", 6 | "function": { 7 | "name": "infer_labels", 8 | "description": "Infer some labels for an entire dataset.", 9 | "strict": true, 10 | "parameters": { 11 | "type": "object", 12 | "properties": { 13 | "labels": { 14 | "type": "array", 15 | "items": { 16 | "type": "string" 17 | }, 18 | "description": "A list of label strings summarizing the entire dataset." 19 | } 20 | }, 21 | "required": [ 22 | "labels" 23 | ], 24 | "additionalProperties": false 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /examples/tests/integration_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import subprocess 4 | 5 | # API KEY 6 | os.environ["OPENAI_API_KEY"] = "Your api KEY" 7 | 8 | # Define the relative path to the examples folder 9 | folder_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../examples")) 10 | 11 | # Loop through all files in the folder 12 | for filename in os.listdir(folder_path): 13 | if filename.endswith(".py"): 14 | file_path = os.path.join(folder_path, filename) 15 | print(f"Running {filename}...") 16 | 17 | try: 18 | # Use sys.executable to ensure we run under the same virtual environment 19 | result = subprocess.run( 20 | [sys.executable, file_path], 21 | capture_output=True, 22 | text=True, 23 | check=True, 24 | encoding="utf-8", 25 | ) 26 | print(f"{filename} executed successfully!\nOutput:\n{result.stdout}") 27 | except subprocess.CalledProcessError as e: 28 | print(f"Error while executing {filename}:\n{e.stderr}") -------------------------------------------------------------------------------- /flashlearn.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | LICENSE.md 2 | README.md 3 | pyproject.toml 4 | flashlearn/__init__.py 5 | flashlearn.egg-info/PKG-INFO 6 | flashlearn.egg-info/SOURCES.txt 7 | flashlearn.egg-info/dependency_links.txt 8 | flashlearn.egg-info/requires.txt 9 | flashlearn.egg-info/top_level.txt 10 | flashlearn/core/__init__.py 11 | flashlearn/core/flash_client.py 12 | flashlearn/core/orchestration.py 13 | flashlearn/core/tests/test_flash_client.py 14 | flashlearn/core/tests/test_orchestration.py 15 | flashlearn/skills/__init__.py 16 | flashlearn/skills/base_data_skill.py 17 | flashlearn/skills/base_skill.py 18 | flashlearn/skills/classification.py 19 | flashlearn/skills/collect_all_code.py 20 | flashlearn/skills/discover_labels.py 21 | flashlearn/skills/general_skill.py 22 | flashlearn/skills/learn_skill.py 23 | flashlearn/skills/tests/test_base_data_skill.py 24 | flashlearn/skills/tests/test_base_skill.py 25 | flashlearn/skills/tests/test_classification.py 26 | flashlearn/skills/tests/test_discover_labels.py 27 | flashlearn/skills/tests/test_general_skill.py 28 | flashlearn/skills/tests/test_learn_skill.py 29 | flashlearn/skills/toolkit/__init__.py 30 | flashlearn/skills/toolkit/generate_init.py 31 | flashlearn/skills/toolkit/generate_toolkit.py 32 | flashlearn/skills/toolkit/simple_search.py 33 | flashlearn/skills/toolkit/validate_json_files.py 34 | flashlearn/utils/__init__.py 35 | flashlearn/utils/demo_data.py 36 | flashlearn/utils/logging_utils.py 37 | flashlearn/utils/token_utils.py 38 | flashlearn/utils/tests/test_demo_data.py 39 | flashlearn/utils/tests/test_token_utils.py -------------------------------------------------------------------------------- /flashlearn.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /flashlearn.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | aiohappyeyeballs>=2.4.4 2 | pillow>=11.1.0 3 | aiohttp>=3.11.11 4 | aiosignal>=1.3.2 5 | annotated-types>=0.7.0 6 | anyio>=4.8.0 7 | attrs>=24.3.0 8 | certifi>=2024.12.14 9 | charset-normalizer>=3.4.1 10 | click>=8.1.8 11 | colorama>=0.4.6 12 | distro>=1.9.0 13 | filelock>=3.17.0 14 | frozenlist>=1.5.0 15 | fsspec>=2024.12.0 16 | h11>=0.14.0 17 | httpcore>=1.0.7 18 | httpx>=0.27.2 19 | huggingface-hub>=0.27.1 20 | idna>=3.10 21 | importlib_metadata>=8.6.1 22 | Jinja2>=3.1.5 23 | jiter>=0.8.2 24 | joblib>=1.4.2 25 | jsonschema>=4.23.0 26 | jsonschema-specifications>=2024.10.1 27 | kagglehub>=0.3.6 28 | litellm>=1.59.3 29 | MarkupSafe>=3.0.2 30 | multidict>=6.1.0 31 | openai>=1.60.0 32 | packaging>=24.2 33 | propcache>=0.2.1 34 | pydantic>=2.10.5 35 | pydantic_core>=2.27.2 36 | python-dateutil>=2.9.0.post0 37 | python-dotenv>=1.0.1 38 | pytz>=2024.2 39 | PyYAML>=6.0.2 40 | referencing>=0.36.1 41 | regex>=2024.11.6 42 | requests>=2.32.3 43 | rpds-py>=0.22.3 44 | six>=1.17.0 45 | sniffio>=1.3.1 46 | threadpoolctl>=3.5.0 47 | tiktoken>=0.8.0 48 | tokenizers>=0.21.0 49 | tqdm>=4.67.1 50 | typing_extensions>=4.12.2 51 | tzdata>=2025.1 52 | urllib3>=2.3.0 53 | yarl>=1.18.3 54 | zipp>=3.21.0 55 | -------------------------------------------------------------------------------- /flashlearn.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | flashlearn 2 | -------------------------------------------------------------------------------- /flashlearn/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/flashlearn/__init__.py -------------------------------------------------------------------------------- /flashlearn/core/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Initialize the 'core' subpackage of FlashLearn. 3 | 4 | This file ensures 'core' is recognized as a subpackage. 5 | You can expose certain classes/functions here if desired. 6 | """ 7 | 8 | # Example of importing necessary modules from the subpackage: 9 | from .flash_client import FlashLiteLLMClient 10 | from .orchestration import StatusTracker, ParallelTask, append_to_jsonl, token_count_for_task, run_task_with_timeout, \ 11 | process_tasks_in_parallel 12 | 13 | __all__ = [ 14 | 'FlashLiteLLMClient', 15 | ] -------------------------------------------------------------------------------- /flashlearn/core/flash_client.py: -------------------------------------------------------------------------------- 1 | import litellm 2 | 3 | class FlashLiteLLMClient: 4 | def __init__(self): 5 | pass 6 | 7 | class Chat: 8 | # Define the Completions class inside Chat 9 | class Completions: 10 | @staticmethod 11 | def create(**kwargs): 12 | # This function passes kwargs to litellm's completion method 13 | # Replace 'litellm.completion' with the actual function path if incorrect 14 | kwargs.update({'no-log': True}) 15 | return litellm.completion(**kwargs) 16 | 17 | # Expose completions as a property of Chat 18 | @property 19 | def completions(self): 20 | return self.Completions() 21 | 22 | # Create an instance of Chat as a class attribute 23 | chat = Chat() 24 | 25 | -------------------------------------------------------------------------------- /flashlearn/core/tests/test_flash_client.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from unittest.mock import patch, MagicMock 3 | import litellm 4 | 5 | from flashlearn.core import FlashLiteLLMClient 6 | 7 | 8 | # Import your classes 9 | 10 | def test_flash_lite_llm_client_init(): 11 | """ 12 | Test that we can instantiate FlashLiteLLMClient without error. 13 | """ 14 | client = FlashLiteLLMClient() 15 | assert client is not None 16 | 17 | def test_flash_lite_llm_client_chat_property(): 18 | """ 19 | Test that 'chat' property returns an object that has a 'completions' property. 20 | """ 21 | client = FlashLiteLLMClient() 22 | # Check that client.chat is created successfully 23 | chat_obj = client.chat 24 | assert hasattr(chat_obj, "completions"), "Chat should have a 'completions' property" 25 | 26 | def test_flash_lite_llm_client_chat_completions_property(): 27 | """ 28 | Test that chat.completions is an instance of Completions, 29 | which should have a 'create' method. 30 | """ 31 | client = FlashLiteLLMClient() 32 | completions_obj = client.chat.completions 33 | assert hasattr(completions_obj, "create"), "Completions should have a 'create' method" 34 | 35 | @patch("litellm.completion", return_value="mocked response") 36 | def test_flash_lite_llm_client_chat_completions_create(mock_completion): 37 | """ 38 | Ensure that Completions.create calls litellm.completion with the correct kwargs, 39 | adding {'no-log': True} automatically. 40 | """ 41 | client = FlashLiteLLMClient() 42 | result = client.chat.completions.create(model="gpt-4", prompt="Hello Test") 43 | 44 | # Verify we patched litellm.completion correctly 45 | mock_completion.assert_called_once() 46 | # Extract the actual arguments passed to litellm.completion 47 | call_args, call_kwargs = mock_completion.call_args 48 | # We expect no positional args, only kwargs 49 | assert call_args == () 50 | # Ensure 'no-log' was added and set to True 51 | assert call_kwargs["no-log"] is True 52 | # Check other expected kwargs 53 | assert call_kwargs["model"] == "gpt-4" 54 | assert call_kwargs["prompt"] == "Hello Test" 55 | 56 | # Finally check the mocked return value 57 | assert result == "mocked response" -------------------------------------------------------------------------------- /flashlearn/skills/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Initialize the 'skills' subpackage of FlashLearn. 3 | 4 | This file ensures 'skills' is recognized as a subpackage. 5 | You can expose certain classes/functions here if desired. 6 | """ 7 | 8 | # Example of importing modules or classes: 9 | from .base_skill import BaseSkill 10 | from .classification import ClassificationSkill 11 | from .discover_labels import DiscoverLabelsSkill 12 | from .general_skill import GeneralSkill 13 | 14 | __all__ = [ 15 | 'BaseSkill', 16 | 'ClassificationSkill', 17 | 'DiscoverLabelsSkill', 18 | 'GeneralSkill', 19 | ] -------------------------------------------------------------------------------- /flashlearn/skills/__pycache__/learn_skill.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/flashlearn/skills/__pycache__/learn_skill.cpython-312.pyc -------------------------------------------------------------------------------- /flashlearn/skills/base_data_skill.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | from typing import List, Dict, Any 3 | import ast 4 | 5 | from .base_skill import BaseSkill 6 | 7 | 8 | class BaseDataSkill(BaseSkill, ABC): 9 | """ 10 | Extends the plain BaseSkill with convenient methods for: 11 | • Building request blocks (system/user) from a list of dictionaries 12 | (where each dict is treated as one "row"). 13 | • Converting key-value pairs into text/image/audio blocks using 14 | column_modalities to decide how each field is processed. 15 | • (Optionally) flattening the blocks for debugging or token usage. 16 | • Merging an output_modality parameter (text/audio/image). 17 | 18 | NOTE: We no longer use pandas.DataFrame or 'columns'; instead, we take a list of 19 | dictionaries (df). Each dict's keys are “columns.” The column_modalities dict 20 | still indicates how each key should be processed (default "text"). 21 | """ 22 | 23 | def build_output_params(self, modality: str) -> Dict[str, Any]: 24 | """ 25 | Return extra top-level fields for the Chat Completions call: 26 | - 'modalities': [...] 27 | - If audio → specify 'audio' config 28 | - If image → specify 'image' config 29 | - Otherwise default to text 30 | """ 31 | if modality == "audio": 32 | return { 33 | "modalities": ["text", "audio"], 34 | "audio": { 35 | "voice": "alloy", 36 | "format": "wav" 37 | } 38 | } 39 | elif modality == "image": 40 | return { 41 | "modalities": ["image"] 42 | } 43 | else: 44 | return { 45 | "modalities": ["text"] 46 | } 47 | 48 | def build_content_blocks( 49 | self, 50 | row: Dict[str, Any], 51 | column_modalities: Dict[str, str] = None 52 | ) -> List[Dict[str, Any]]: 53 | """ 54 | Given one “row” (a dict), produce a list of content blocks: 55 | - { "type": "text", "text": ... } 56 | - { "type": "image_url", "image_url": {"url": ...} } 57 | - { "type": "input_audio", "input_audio": { "data": ..., "format": ... } } 58 | 59 | Falls back to 'text' if modality is missing or invalid. 60 | """ 61 | if column_modalities is None: 62 | column_modalities = {} 63 | content_blocks = [] 64 | 65 | for key, val in row.items(): 66 | raw_value = str(val).strip() 67 | if not raw_value: 68 | continue 69 | 70 | modality = column_modalities.get(key, "text") 71 | 72 | if modality == "text": 73 | content_blocks.append({"type": "text", "text": raw_value}) 74 | 75 | elif modality == "audio": 76 | content_blocks.append({ 77 | "type": "input_audio", 78 | "input_audio": { 79 | "data": raw_value, 80 | "format": "wav" 81 | } 82 | }) 83 | 84 | elif modality == "image_url": 85 | content_blocks.append({ 86 | "type": "image_url", 87 | "image_url": {"url": raw_value} 88 | }) 89 | 90 | elif modality == "image_base64": 91 | # Simple heuristic for JPEG vs PNG base64 prefix 92 | prefix = "data:image/jpeg;base64," if raw_value.startswith("/9j") else "data:image/png;base64," 93 | content_blocks.append({ 94 | "type": "image_url", 95 | "image_url": {"url": prefix + raw_value} 96 | }) 97 | 98 | else: 99 | # Fallback: treat as text 100 | content_blocks.append({"type": "text", "text": raw_value}) 101 | 102 | return content_blocks 103 | 104 | def flatten_blocks_for_debug(self, blocks: List[Dict[str, Any]]) -> str: 105 | """ 106 | Returns a textual representation of content blocks—useful 107 | for debugging or approximate token counting logic. 108 | """ 109 | lines = [] 110 | for b in blocks: 111 | if b["type"] == "text": 112 | lines.append(b["text"]) 113 | elif b["type"] == "image_url": 114 | lines.append("[IMAGE_URL]") 115 | elif b["type"] == "input_audio": 116 | lines.append("[AUDIO]") 117 | else: 118 | lines.append(f"[{b['type'].upper()}]") 119 | return "\n".join(lines) 120 | 121 | def create_tasks( 122 | self, 123 | df: List[Dict[str, Any]], 124 | column_modalities: Dict[str, str] = None, 125 | output_modality: str = "text", 126 | **kwargs 127 | ) -> List[Dict[str, Any]]: 128 | """ 129 | Default implementation: one dictionary = one task. If needed, 130 | child classes can override for alternative grouping strategies. 131 | 132 | :param df: A list of dicts; each dict is treated as a 'row'. 133 | :param column_modalities: Mapping from key → "text"/"image_url"/"audio"/etc. 134 | :param output_modality: "text", "audio", or "image" 135 | :param columns: (Unused) Only present for signature consistency with the original. 136 | :param kwargs: Additional keyword arguments, if any. 137 | :return: A list of tasks (each task a dict with {custom_id, request}). 138 | """ 139 | if column_modalities is None: 140 | column_modalities = {} 141 | if output_modality != "text": 142 | output_params = self.build_output_params(output_modality) 143 | else: 144 | output_params = {} 145 | 146 | tasks = [] 147 | for idx, row in enumerate(df): 148 | content_blocks = self.build_content_blocks(row, column_modalities) 149 | if not content_blocks: 150 | continue 151 | 152 | user_text_for_prompt = self.flatten_blocks_for_debug(content_blocks) 153 | 154 | system_msg = { 155 | "role": "system", 156 | "content": self.system_prompt, 157 | "content_str": self.system_prompt 158 | } 159 | user_msg = { 160 | "role": "user", 161 | "content": content_blocks, 162 | "content_str": user_text_for_prompt 163 | } 164 | 165 | request_body = { 166 | "model": self.model_name, 167 | "messages": [system_msg, user_msg], 168 | "tools": [self._build_function_def()], 169 | "tool_choice": "required" 170 | } 171 | request_body.update(output_params) 172 | 173 | tasks.append({ 174 | "custom_id": str(idx), 175 | "request": request_body 176 | }) 177 | 178 | return tasks 179 | 180 | def parse_result(self, raw_result: Dict[str, Any]) -> Any: 181 | """ 182 | By default, just return the raw result as-is. 183 | Child classes often override with more nuanced parsing. 184 | """ 185 | return raw_result 186 | 187 | def parse_function_call(self, raw_result: Dict[str, Any], arg_name="categories") -> Any: 188 | """ 189 | Helper to parse a function call argument from “raw_result”. 190 | 191 | Example: looks for a top-level 'function_call' under 'choices[0]["message"]', 192 | then tries to parse JSON from 'arguments', returning the key specified 193 | by arg_name (e.g., "categories"). 194 | """ 195 | try: 196 | message = raw_result["choices"][0]["message"] 197 | if "function_call" not in message: 198 | return None 199 | args_str = message["function_call"].get("arguments", "") 200 | if not args_str: 201 | return None 202 | args_obj = ast.literal_eval(args_str) 203 | return args_obj.get(arg_name, None) 204 | except Exception: 205 | return None 206 | 207 | def _build_function_def(self) -> Dict[str, Any]: 208 | """ 209 | Minimal default function definition. 210 | Child classes may override with more specialized JSON schemas. 211 | """ 212 | return { 213 | "type": "function", 214 | "function": { 215 | "name": "basic_function", 216 | "description": "A simple function call placeholder.", 217 | "strict": True, 218 | "parameters": { 219 | "type": "object", 220 | "properties": { 221 | "result": { 222 | "type": "string" 223 | } 224 | }, 225 | "required": ["result"], 226 | "additionalProperties": False 227 | } 228 | } 229 | } -------------------------------------------------------------------------------- /flashlearn/skills/base_skill.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from abc import ABC, abstractmethod 3 | import json 4 | import os 5 | from typing import List, Dict, Any 6 | 7 | from flashlearn.core.flash_client import FlashLiteLLMClient 8 | from flashlearn.core.orchestration import process_tasks_in_parallel 9 | from flashlearn.utils.token_utils import count_tokens_for_tasks 10 | 11 | 12 | class BaseSkill(ABC): 13 | """ 14 | Abstract base for any 'skill' that FlashLearn can execute. 15 | Enforces a consistent interface for building tasks, parsing results, 16 | and building function definitions for function calling. 17 | 18 | NOTE: This version expects inputs as a list of dictionaries 19 | where each dictionary is treated as a "row" with key/value pairs 20 | corresponding to columns/fields. The default "column modality" is "text", 21 | but you can specify others as needed in child classes that implement 22 | create_tasks(). 23 | """ 24 | 25 | def __init__(self, model_name: str, system_prompt: str = '', full_row=False, client=FlashLiteLLMClient()): 26 | self.model_name = model_name 27 | self.client = client 28 | self.system_prompt = system_prompt 29 | self.full_row = full_row 30 | self.total_input_tokens = 0 31 | self.total_output_tokens = 0 32 | 33 | @abstractmethod 34 | def create_tasks(self, df, **kwargs) -> List[Dict[str, Any]]: 35 | """ 36 | Build a list of tasks for the given data, which is expected to be 37 | a list of dictionaries (df). Each dict is treated as one "row" 38 | containing key/value pairs for the data fields. 39 | """ 40 | raise NotImplementedError() 41 | 42 | @abstractmethod 43 | def _build_function_def(self) -> Dict[str, Any]: 44 | """ 45 | Return the function definition (with JSON schema) that the model will use. 46 | """ 47 | raise NotImplementedError() 48 | 49 | def save(self, filepath: str = None): 50 | """ 51 | Save this skill's function definition (and any relevant metadata) to JSON. 52 | 53 | :param filepath: Optional path. Defaults to .json in current directory. 54 | :return: The dictionary that was saved. 55 | 56 | NOTE: model_name is deliberately excluded from the saved JSON as requested. 57 | """ 58 | if filepath is None: 59 | filepath = f"{self.__class__.__name__}.json" 60 | 61 | definition_out = { 62 | "skill_class": self.__class__.__name__, 63 | "system_prompt": self.system_prompt, 64 | "function_definition": self._build_function_def() 65 | # model_name is intentionally omitted 66 | } 67 | 68 | with open(filepath, "w", encoding="utf-8") as f: 69 | json.dump(definition_out, f, indent=2) 70 | 71 | print(f"Skill definition saved to: {os.path.abspath(filepath)}") 72 | return definition_out 73 | 74 | def run_tasks_in_parallel( 75 | self, 76 | tasks: list, 77 | save_filepath: str = None, 78 | max_requests_per_minute=999, 79 | max_tokens_per_minute=999999, 80 | max_attempts=2, 81 | token_encoding_name="cl100k_base", 82 | return_results=True, 83 | request_timeout=60, 84 | ): 85 | """ 86 | Orchestrates tasks in parallel using process_tasks_in_parallel. 87 | 88 | :param tasks: The list of tasks to run. 89 | :param save_filepath: Where to save partial progress or results (optional). 90 | :param max_requests_per_minute: Throttle for requests/min. 91 | :param max_tokens_per_minute: Throttle for tokens/min. 92 | :param max_attempts: How many times to attempt a failed request before giving up. 93 | :param token_encoding_name: The token encoding name (e.g., cl100k_base). 94 | :param return_results: Whether to return the final results. 95 | :param request_timeout: Timeout for each request. 96 | :return: (final_results, final_status_tracker). 97 | """ 98 | final_results, final_status = asyncio.run( 99 | process_tasks_in_parallel( 100 | return_results=return_results, 101 | client=self.client, 102 | tasks_data=tasks, 103 | save_filepath=save_filepath, 104 | max_requests_per_minute=max_requests_per_minute, 105 | max_tokens_per_minute=max_tokens_per_minute, 106 | max_attempts=max_attempts, 107 | token_encoding_name=token_encoding_name, 108 | request_timeout=request_timeout, 109 | 110 | ) 111 | ) 112 | # Update usage statistics from the status tracker 113 | self.total_input_tokens = getattr(final_status, "total_input_tokens", 0) 114 | self.total_output_tokens = getattr(final_status, "total_output_tokens", 0) 115 | return final_results 116 | 117 | def estimate_tasks_cost(self, tasks: list) -> float: 118 | """ 119 | Return an approximate cost of tasks, based on # tokens * rate. 120 | Adjust the rate to match your model's actual pricing. 121 | """ 122 | total_tokens = count_tokens_for_tasks(tasks, self.model_name) 123 | # Example: GPT-4 prompt rate might be ~$0.03 / 1K tokens 124 | return total_tokens * 1.5 -------------------------------------------------------------------------------- /flashlearn/skills/classification.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, List 2 | from flashlearn.core import FlashLiteLLMClient 3 | from flashlearn.skills.base_data_skill import BaseDataSkill 4 | 5 | class ClassificationSkill(BaseDataSkill): 6 | """ 7 | A skill that classifies text from each input dictionary into one or more known categories. 8 | Optionally set max_categories = -1 if you want unlimited picks. 9 | """ 10 | 11 | def __init__( 12 | self, 13 | model_name: str, 14 | categories: List[str], 15 | max_categories: int = 1, 16 | system_prompt: str = "", 17 | client=FlashLiteLLMClient() 18 | ): 19 | super().__init__(model_name=model_name, system_prompt=system_prompt, client=client) 20 | self.categories = categories 21 | self.max_categories = max_categories 22 | 23 | def _build_function_def(self) -> Dict[str, Any]: 24 | """ 25 | Overridden to produce a JSON schema requiring “categories”. 26 | If max_categories == 1, we expect a single string category. 27 | Otherwise, we allow an array of category strings. 28 | """ 29 | if self.max_categories == 1: 30 | prop_def = { 31 | "type": "string", 32 | "enum": self.categories, 33 | "description": "A chosen category from the provided set." 34 | } 35 | else: 36 | prop_def = { 37 | "type": "array", 38 | "items": { 39 | "type": "string", 40 | "enum": self.categories 41 | }, 42 | "description": "A list of chosen categories from the provided set." 43 | } 44 | if self.max_categories > 0: 45 | prop_def["maxItems"] = self.max_categories 46 | 47 | return { 48 | "type": "function", 49 | "function": { 50 | "name": "categorize_text", 51 | "description": ( 52 | f"Classify text into up to {self.max_categories} categories from a given list." 53 | if self.max_categories != 1 54 | else f"Classify text into exactly 1 category out of {self.categories}." 55 | ), 56 | "strict": True, 57 | "parameters": { 58 | "type": "object", 59 | "properties": { 60 | "categories": prop_def 61 | }, 62 | "required": ["categories"], 63 | "additionalProperties": False 64 | } 65 | } 66 | } 67 | 68 | def parse_result(self, raw_result: Dict[str, Any]) -> Any: 69 | """ 70 | Extract the 'categories' result we specified in the function schema. 71 | Returns a list of categories (even if only one was chosen). 72 | """ 73 | categories_ret = self.parse_function_call(raw_result, arg_name="categories") 74 | if not categories_ret: 75 | return [] 76 | # If the schema was string for single-cat, convert to list 77 | if isinstance(categories_ret, str): 78 | return [categories_ret] 79 | return categories_ret -------------------------------------------------------------------------------- /flashlearn/skills/collect_all_code.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def read_python_files_from_specific_folders(top_level_folder, target_folders): 4 | """ 5 | Gathers all .py files from each of the specified folders (and subfolders), 6 | returning a single string with the path and source code. 7 | """ 8 | code_collection = [] 9 | 10 | # For each target folder, construct its full path, then walk only inside it. 11 | for folder_name in target_folders: 12 | folder_path = os.path.join(top_level_folder, folder_name) 13 | 14 | # Make sure the child folder actually exists before walking it. 15 | if os.path.isdir(folder_path): 16 | for root, dirs, files in os.walk(folder_path): 17 | for file in files: 18 | if file.endswith('.py'): # Only .py files 19 | file_path = os.path.join(root, file) 20 | try: 21 | with open(file_path, 'r', encoding='utf-8') as f: 22 | file_content = f.read() 23 | # Get the path relative to top_level_folder for clarity 24 | relative_path = os.path.relpath(file_path, top_level_folder) 25 | # Append the file name and content 26 | code_collection.append(f"File: {relative_path}\n{file_content}\n") 27 | except (IOError, OSError) as e: 28 | print(f"Could not read file {file_path}: {e}") 29 | else: 30 | print(f"Warning: Folder '{folder_name}' does not exist at '{folder_path}'.") 31 | 32 | # Combine all collected code into a single text 33 | return "\n".join(code_collection) 34 | 35 | 36 | # Example usage 37 | if __name__ == "__main__": 38 | top_level_folder = r'C:/Users/Gal/PycharmProjects/FlashLearn' 39 | target_folders = ['examples'] 40 | code_as_string = read_python_files_from_specific_folders(top_level_folder, target_folders) 41 | print(code_as_string) -------------------------------------------------------------------------------- /flashlearn/skills/discover_labels.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict, Any 2 | from flashlearn.core import FlashLiteLLMClient 3 | from flashlearn.skills.base_data_skill import BaseDataSkill 4 | 5 | 6 | class DiscoverLabelsSkill(BaseDataSkill): 7 | """ 8 | Example of a skill that lumps all rows (dicts) together into a 9 | single user message for discovering labels across the entire dataset. 10 | """ 11 | 12 | def __init__( 13 | self, 14 | model_name: str, 15 | label_count: int = -1, 16 | system_prompt: str = "", 17 | client=FlashLiteLLMClient() 18 | ): 19 | super().__init__(model_name=model_name, system_prompt=system_prompt, client=client) 20 | self.label_count = label_count 21 | 22 | def create_tasks( 23 | self, 24 | df: List[Dict[str, Any]], 25 | column_modalities: Dict[str, str] = None, 26 | output_modality: str = "text", 27 | columns: List[str] = None, # Not really used, present for API consistency 28 | **kwargs 29 | ) -> List[Dict[str, Any]]: 30 | """ 31 | Instead of “one row = one task,” we produce “one big task” with 32 | all rows included as a single user message. 33 | :param df: A list of dicts (each dict = one row of data). 34 | :param column_modalities: Which keys get “text”, “image_url”, “audio”, etc. 35 | :param output_modality: "text", "audio", or "image" (passed to build_output_params). 36 | :param columns: Ignored here; kept for signature compatibility. 37 | :return: A single-item list containing one aggregated task, or empty if no content. 38 | """ 39 | if output_modality != "text": 40 | output_params = self.build_output_params(output_modality) 41 | else: 42 | output_params = {} 43 | all_blocks = [] 44 | 45 | # Aggregate content blocks across all rows 46 | for row in df: 47 | row_blocks = self.build_content_blocks(row, column_modalities) 48 | all_blocks.extend(row_blocks) 49 | 50 | if not all_blocks: 51 | return [] 52 | 53 | flattened_str = self.flatten_blocks_for_debug(all_blocks) 54 | 55 | # Possibly mention the label_count in the prompt 56 | if self.label_count > 0: 57 | sys_prompt = ( 58 | f"{self.system_prompt} You may select up to {self.label_count} labels." 59 | ) 60 | else: 61 | sys_prompt = f"{self.system_prompt} You may select any number of labels." 62 | 63 | system_msg = { 64 | "role": "system", 65 | "content": sys_prompt, 66 | "content_str": sys_prompt 67 | } 68 | user_msg = { 69 | "role": "user", 70 | "content": all_blocks, 71 | "content_str": flattened_str 72 | } 73 | 74 | request_body = { 75 | "model": self.model_name, 76 | "messages": [system_msg, user_msg], 77 | "tools": [self._build_function_def()], 78 | "tool_choice": "required" 79 | } 80 | request_body.update(output_params) 81 | 82 | # Return just one aggregated task 83 | return [{ 84 | "custom_id": "0", 85 | "request": request_body 86 | }] 87 | 88 | def _build_function_def(self) -> Dict[str, Any]: 89 | """ 90 | Build a function definition that expects an array of strings labeled 'labels'. 91 | """ 92 | prop_def = { 93 | "type": "array", 94 | "items": {"type": "string"}, 95 | "description": "A list of label strings summarizing the entire dataset." 96 | } 97 | return { 98 | "type": "function", 99 | "function": { 100 | "name": "infer_labels", 101 | "description": "Infer some labels for an entire dataset.", 102 | "strict": True, 103 | "parameters": { 104 | "type": "object", 105 | "properties": { 106 | "labels": prop_def 107 | }, 108 | "required": ["labels"], 109 | "additionalProperties": False 110 | } 111 | } 112 | } 113 | 114 | def parse_result(self, raw_result: Dict[str, Any]) -> Any: 115 | """ 116 | Parse the 'labels' key from function call arguments. 117 | Falls back to an empty list if not present. 118 | """ 119 | return self.parse_function_call(raw_result, arg_name="labels") or [] -------------------------------------------------------------------------------- /flashlearn/skills/general_skill.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Dict, Any, List 3 | 4 | from copy import copy 5 | import ast 6 | import json 7 | 8 | from flashlearn.core import FlashLiteLLMClient 9 | from flashlearn.skills.base_data_skill import BaseDataSkill 10 | 11 | flash_logger = logging.getLogger("FlashLearn") 12 | logging.basicConfig(level=logging.ERROR) 13 | 14 | 15 | class GeneralSkill(BaseDataSkill): 16 | """ 17 | A general-purpose skill that accepts a custom function definition at init. 18 | Overrides only the pieces that differ from the default base logic. 19 | 20 | This version expects its data input as a list of dictionaries (one dict per row). 21 | """ 22 | 23 | def __init__( 24 | self, 25 | model_name: str, 26 | function_definition: Dict[str, Any], 27 | system_prompt: str = "You are a helpful assistant.", 28 | columns: List[str] = None, 29 | client=FlashLiteLLMClient() 30 | ): 31 | super().__init__(model_name=model_name, system_prompt=system_prompt, client=client) 32 | self._function_definition = function_definition 33 | self.columns = columns or [] 34 | 35 | def create_tasks( 36 | self, 37 | df: List[Dict[str, Any]], 38 | column_modalities: Dict[str, str] = None, 39 | output_modality: str = "text", 40 | columns: List[str] = None, 41 | **kwargs 42 | ) -> List[Dict[str, Any]]: 43 | """ 44 | If the user doesn't pass a 'columns' list, we fall back 45 | to self.columns if available. Then pass on to the parent 46 | create_tasks (BaseDataSkill) which handles the logic of converting 47 | each dict into blocks. 48 | 49 | :param df: A list of dicts to process (one dict per row). 50 | :param column_modalities: Mapping key->modality (e.g. text, image_url, etc.) 51 | :param output_modality: "text", "audio", or "image" for the model's response. 52 | :param columns: (Optional) Not used by default logic, but present for consistency. 53 | :param kwargs: Additional arguments if needed. 54 | :return: A list of tasks for parallel processing. 55 | """ 56 | if not columns: 57 | columns = self.columns 58 | 59 | return super().create_tasks( 60 | df=df, 61 | column_modalities=column_modalities, 62 | output_modality=output_modality, 63 | columns=columns, 64 | **kwargs 65 | ) 66 | 67 | def _build_function_def(self) -> Dict[str, Any]: 68 | """ 69 | Return whatever custom function definition was provided at init. 70 | """ 71 | return self._function_definition 72 | 73 | def parse_result(self, raw_result: Dict[str, Any]) -> Any: 74 | """ 75 | By default, just returns the raw result. 76 | Override this if you need more complex parsing. 77 | """ 78 | return raw_result 79 | 80 | @staticmethod 81 | def load_skill(config: Dict[str, Any], model_name="gpt-4o-mini",client=FlashLiteLLMClient()): 82 | """ 83 | Load a dictionary specifying model, prompts, and function definition, then 84 | return an initialized GeneralSkill instance. 85 | 86 | Example config structure: 87 | { 88 | "skill_class": "GeneralSkill", 89 | "model_name": "gpt-4o", 90 | "system_prompt": "Exactly populate the provided function definition", 91 | "function_definition": { 92 | "type": "function", 93 | "function": { 94 | "name": "text_classification", 95 | "description": "Classify text into categories.", 96 | "strict": true, 97 | "parameters": { ... } 98 | } 99 | }, 100 | "columns": ["some_column", "another_column"] 101 | } 102 | """ 103 | model_name = model_name 104 | system_prompt = config.get("system_prompt", "You are a helpful assistant.") 105 | function_definition = config.get("function_definition", {}) 106 | columns = config.get("columns", []) 107 | 108 | return GeneralSkill( 109 | model_name=model_name, 110 | function_definition=function_definition, 111 | system_prompt=system_prompt, 112 | columns=columns, 113 | client=client 114 | ) -------------------------------------------------------------------------------- /flashlearn/skills/tests/.hypothesis/examples/099e569465a026b4/bec021b4f368e306: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flashlearn/skills/tests/.hypothesis/unicode_data/15.0.0/charmap.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/flashlearn/skills/tests/.hypothesis/unicode_data/15.0.0/charmap.json.gz -------------------------------------------------------------------------------- /flashlearn/skills/tests/.hypothesis/unicode_data/15.0.0/codec-utf-8.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/flashlearn/skills/tests/.hypothesis/unicode_data/15.0.0/codec-utf-8.json.gz -------------------------------------------------------------------------------- /flashlearn/skills/tests/test_base_skill.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from unittest.mock import patch, MagicMock, mock_open 3 | 4 | from hypothesis import given, strategies as st 5 | from typing import Dict, Any 6 | 7 | from flashlearn.skills import BaseSkill 8 | 9 | 10 | # A concrete subclass so we can instantiate and test BaseSkill 11 | class MockSkill(BaseSkill): 12 | def create_tasks(self, data, **kwargs): 13 | """ 14 | Minimal mock implementation: 15 | data is expected to be a list of dicts. Each dict is a 'row', 16 | and we produce a task per row, assigning an "id" from the index. 17 | """ 18 | tasks = [] 19 | for i, row in enumerate(data): 20 | tasks.append({ 21 | "id": str(i), 22 | "data": row 23 | }) 24 | return tasks 25 | 26 | def _build_function_def(self) -> Dict[str, Any]: 27 | return { 28 | "name": "mock_function", 29 | "parameters": { 30 | "type": "object", 31 | "properties": { 32 | "example_key": {"type": "string"} 33 | } 34 | } 35 | } 36 | 37 | 38 | @pytest.fixture 39 | def mock_skill(): 40 | """Provides a MockSkill instance for testing.""" 41 | return MockSkill(model_name="gpt-4o-mini", system_prompt="Test prompt") 42 | 43 | 44 | @patch("flashlearn.skills.base_skill.process_tasks_in_parallel") 45 | def test_run_tasks_in_parallel(mock_process, mock_skill): 46 | """ 47 | Ensure run_tasks_in_parallel uses our mock, sets token counts, 48 | and returns results. 49 | """ 50 | mock_process.return_value = ( 51 | ["result_data"], 52 | MagicMock(total_input_tokens=100, total_output_tokens=200) 53 | ) 54 | tasks = [{"id": "1", "data": "some_data"}] 55 | results = mock_skill.run_tasks_in_parallel(tasks) 56 | 57 | assert results == ["result_data"], "Should return the mocked result_data" 58 | assert mock_skill.total_input_tokens == 100 59 | assert mock_skill.total_output_tokens == 200 60 | mock_process.assert_called_once() 61 | 62 | 63 | def test_save_with_filename(mock_skill): 64 | """ 65 | Test that save writes JSON with a provided filename. 66 | We ensure that model_name is not stored, as per requirements. 67 | """ 68 | with patch("builtins.open", mock_open()) as mocked_file, \ 69 | patch("os.path.abspath", return_value="/fake/path"): 70 | mock_skill.save(filepath="custom_skill.json") 71 | mocked_file.assert_called_once_with("custom_skill.json", "w", encoding="utf-8") 72 | 73 | 74 | def test_save_with_default_filename(mock_skill): 75 | """ 76 | Test that save writes JSON using the default class-based filename. 77 | We ensure that model_name is not stored, as per requirements. 78 | """ 79 | with patch("builtins.open", mock_open()) as mocked_file, \ 80 | patch("os.path.abspath", return_value="/fake/path"): 81 | mock_skill.save() # No filepath passed, should use MockSkill.json 82 | mocked_file.assert_called_once_with("MockSkill.json", "w", encoding="utf-8") 83 | 84 | 85 | def test_create_tasks(mock_skill): 86 | """ 87 | Test that create_tasks returns a list of dicts and each dict 88 | corresponds to the input 'rows' in some manner. 89 | """ 90 | data = [ 91 | {"col1": 10, "col2": "foo"}, 92 | {"col1": 20, "col2": "bar"} 93 | ] 94 | tasks = mock_skill.create_tasks(data) 95 | assert len(tasks) == 2 96 | assert tasks[0]["id"] == "0" 97 | assert tasks[1]["id"] == "1" 98 | 99 | 100 | def test_build_function_def(mock_skill): 101 | """ 102 | Test the mocked _build_function_def returns the expected dict. 103 | """ 104 | func_def = mock_skill._build_function_def() 105 | assert "name" in func_def 106 | assert "parameters" in func_def 107 | 108 | @patch("flashlearn.skills.base_skill.process_tasks_in_parallel") 109 | def test_run_tasks_in_parallel_no_final_status(mock_process, mock_skill): 110 | """ 111 | Ensures that if process_tasks_in_parallel returns None for final_status, 112 | the default (0) tokens are set. 113 | """ 114 | mock_process.return_value = (["some_data"], None) 115 | results = mock_skill.run_tasks_in_parallel(tasks=[]) 116 | assert results == ["some_data"] 117 | assert mock_skill.total_input_tokens == 0, "Should be 0 by default if final_status is None" 118 | assert mock_skill.total_output_tokens == 0, "Should be 0 by default if final_status is None" 119 | 120 | 121 | @patch("flashlearn.skills.base_skill.process_tasks_in_parallel") 122 | def test_run_tasks_in_parallel_custom_arguments(mock_process, mock_skill): 123 | """ 124 | Ensures that the optional arguments (max_requests_per_minute, max_tokens_per_minute, etc.) 125 | are passed through, and lines 71-73 are exercised. 126 | """ 127 | mock_process.return_value = ( 128 | ["some_data"], 129 | MagicMock(total_input_tokens=10, total_output_tokens=20) 130 | ) 131 | results = mock_skill.run_tasks_in_parallel( 132 | tasks=[{"id": "123"}], 133 | save_filepath="test.json", 134 | max_requests_per_minute=100, 135 | max_tokens_per_minute=200, 136 | max_attempts=3, 137 | token_encoding_name="test_tokens", 138 | return_results=False, 139 | request_timeout=10, 140 | ) 141 | assert results == ["some_data"] 142 | assert mock_skill.total_input_tokens == 10 143 | assert mock_skill.total_output_tokens == 20 144 | mock_process.assert_called_once_with( 145 | return_results=False, 146 | client=mock_skill.client, 147 | tasks_data=[{"id": "123"}], 148 | save_filepath="test.json", 149 | max_requests_per_minute=100, 150 | max_tokens_per_minute=200, 151 | max_attempts=3, 152 | token_encoding_name="test_tokens", 153 | request_timeout=10, 154 | ) 155 | 156 | 157 | def test_estimate_tasks_cost(mock_skill): 158 | """ 159 | Exercises estimate_tasks_cost to ensure lines 85–89 execute. 160 | """ 161 | # We don't care about the exact cost, just that it doesn't crash and is >= 0 162 | tasks = [{"prompt": "Hello world"}] 163 | cost = mock_skill.estimate_tasks_cost(tasks) 164 | assert cost >= 0, "Cost should be a non-negative float" -------------------------------------------------------------------------------- /flashlearn/skills/tests/test_classification.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from unittest.mock import MagicMock 3 | 4 | from flashlearn.skills import ClassificationSkill 5 | 6 | @pytest.fixture 7 | def single_cat_skill(): 8 | """ 9 | Skill configured with max_categories=1 => single category selection. 10 | """ 11 | return ClassificationSkill( 12 | model_name="gpt-4", 13 | categories=["CategoryA", "CategoryB"], 14 | max_categories=1, 15 | system_prompt="Single category prompt" 16 | ) 17 | 18 | @pytest.fixture 19 | def multi_cat_skill(): 20 | """ 21 | Skill configured with max_categories=3 => can pick up to 3 categories. 22 | """ 23 | return ClassificationSkill( 24 | model_name="gpt-4", 25 | categories=["Cat1", "Cat2", "Cat3", "Cat4"], 26 | max_categories=3, 27 | system_prompt="Multi category prompt" 28 | ) 29 | 30 | @pytest.fixture 31 | def unlimited_cat_skill(): 32 | """ 33 | Skill configured with max_categories=-1 => unlimited picks in array form. 34 | """ 35 | return ClassificationSkill( 36 | model_name="gpt-4", 37 | categories=["X", "Y", "Z"], 38 | max_categories=-1, 39 | system_prompt="Unlimited category prompt" 40 | ) 41 | 42 | def test_instantiate_single_cat_skill(single_cat_skill): 43 | """ 44 | Simple check that our single-category skill is instantiated 45 | with the correct fields. 46 | """ 47 | assert single_cat_skill.model_name == "gpt-4" 48 | assert single_cat_skill.categories == ["CategoryA", "CategoryB"] 49 | assert single_cat_skill.max_categories == 1 50 | assert single_cat_skill.system_prompt == "Single category prompt" 51 | 52 | def test_instantiate_multi_cat_skill(multi_cat_skill): 53 | """ 54 | Verify multi-cat skill’s fields. 55 | """ 56 | assert multi_cat_skill.model_name == "gpt-4" 57 | assert multi_cat_skill.categories == ["Cat1", "Cat2", "Cat3", "Cat4"] 58 | assert multi_cat_skill.max_categories == 3 59 | 60 | def test_instantiate_unlimited_cat_skill(unlimited_cat_skill): 61 | """ 62 | Verify unlimited-cat skill’s fields. 63 | """ 64 | assert unlimited_cat_skill.max_categories == -1 65 | 66 | def test_build_function_def_single_cat(single_cat_skill): 67 | """ 68 | If max_categories=1 => the schema property is type=string with an enum of the categories. 69 | """ 70 | func_def = single_cat_skill._build_function_def() 71 | assert func_def["type"] == "function" 72 | assert "function" in func_def 73 | 74 | fdef = func_def["function"] 75 | assert "parameters" in fdef 76 | 77 | props = fdef["parameters"]["properties"] 78 | assert "categories" in props 79 | 80 | cat_prop = props["categories"] 81 | assert cat_prop["type"] == "string" 82 | assert cat_prop["enum"] == ["CategoryA", "CategoryB"] 83 | # No 'maxItems' since it's a string schema 84 | 85 | def test_build_function_def_multi_cat(multi_cat_skill): 86 | """ 87 | If max_categories=3 => the schema property is an array with items=enum, and maxItems=3. 88 | """ 89 | func_def = multi_cat_skill._build_function_def() 90 | fdef = func_def["function"] 91 | cat_prop = fdef["parameters"]["properties"]["categories"] 92 | 93 | assert cat_prop["type"] == "array" 94 | assert cat_prop["items"]["enum"] == ["Cat1", "Cat2", "Cat3", "Cat4"] 95 | assert cat_prop["maxItems"] == 3 96 | 97 | def test_build_function_def_unlimited_cat(unlimited_cat_skill): 98 | """ 99 | If max_categories=-1 => the schema property is an array with no maxItems. 100 | """ 101 | func_def = unlimited_cat_skill._build_function_def() 102 | cat_prop = func_def["function"]["parameters"]["properties"]["categories"] 103 | 104 | assert cat_prop["type"] == "array" 105 | assert "maxItems" not in cat_prop, "Should not have maxItems if max_categories is -1." 106 | 107 | def test_parse_result_none(single_cat_skill): 108 | """ 109 | parse_function_call returns None => parse_result returns []. 110 | """ 111 | single_cat_skill.parse_function_call = MagicMock(return_value=None) 112 | output = single_cat_skill.parse_result({}) 113 | assert output == [], "If parse_function_call None => empty list" 114 | 115 | def test_parse_result_single_string(single_cat_skill): 116 | """ 117 | parse_function_call returns a string => parse_result wraps it in a list. 118 | """ 119 | single_cat_skill.parse_function_call = MagicMock(return_value="OnlyOneCat") 120 | output = single_cat_skill.parse_result({}) 121 | assert output == ["OnlyOneCat"] 122 | 123 | def test_parse_result_list(multi_cat_skill): 124 | """ 125 | parse_function_call returns a list => parse_result returns that list as-is. 126 | """ 127 | multi_cat_skill.parse_function_call = MagicMock(return_value=["Cat1", "Cat3"]) 128 | output = multi_cat_skill.parse_result({}) 129 | assert output == ["Cat1", "Cat3"] 130 | 131 | def test_create_tasks_inherits_behavior(multi_cat_skill): 132 | """ 133 | Just verify we can call create_tasks from the base class logic and get tasks. 134 | ClassificationSkill doesn’t override create_tasks, so it should produce 135 | tasks just like the parent. We pass a list of dicts (no DataFrame). 136 | """ 137 | data = [ 138 | {"text": "Hello world"}, 139 | {"text": ""} 140 | ] 141 | tasks = multi_cat_skill.create_tasks(data) 142 | # Should produce 1 task (the second dict is empty => skip). 143 | assert len(tasks) == 1 144 | assert tasks[0]["custom_id"] == "0" 145 | 146 | req = tasks[0]["request"] 147 | # Confirm the classification function definition is used 148 | assert req["tools"][0]["function"]["name"] == "categorize_text" -------------------------------------------------------------------------------- /flashlearn/skills/tests/test_discover_labels.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from unittest.mock import MagicMock 3 | 4 | from flashlearn.skills import DiscoverLabelsSkill 5 | 6 | @pytest.fixture 7 | def discover_skill_limited(): 8 | """ 9 | DiscoverLabelsSkill with label_count=3 (i.e., user can select up to 3 labels). 10 | """ 11 | return DiscoverLabelsSkill( 12 | model_name="gpt-4", 13 | label_count=3, 14 | system_prompt="Label the dataset please." 15 | ) 16 | 17 | @pytest.fixture 18 | def discover_skill_unlimited(): 19 | """ 20 | DiscoverLabelsSkill with label_count=-1 => no limit. 21 | """ 22 | return DiscoverLabelsSkill( 23 | model_name="gpt-4", 24 | label_count=-1, 25 | system_prompt="Unlimited labeling." 26 | ) 27 | 28 | def test_instantiate_discover_skill(discover_skill_limited): 29 | """ 30 | Verify basic instantiation fields. 31 | """ 32 | assert discover_skill_limited.model_name == "gpt-4" 33 | assert discover_skill_limited.label_count == 3 34 | assert discover_skill_limited.system_prompt == "Label the dataset please." 35 | 36 | def test_create_tasks_limited(discover_skill_limited): 37 | """ 38 | create_tasks should aggregate all rows into one request. 39 | label_count=3 => system prompt includes 'You may select up to 3 labels.' 40 | """ 41 | data = [ 42 | {"col1": "data 1"}, 43 | {"col1": "data 2"} 44 | ] 45 | tasks = discover_skill_limited.create_tasks(data) 46 | assert len(tasks) == 1, "Should produce exactly one task." 47 | 48 | task = tasks[0] 49 | assert task["custom_id"] == "0" 50 | 51 | req = task["request"] 52 | assert req["model"] == "gpt-4" 53 | # Tools => function def named 'infer_labels' 54 | assert req["tools"][0]["function"]["name"] == "infer_labels" 55 | 56 | # System prompt appended with label_count 57 | assert "up to 3 labels" in req["messages"][0]["content"] 58 | 59 | # User message should combine the 2 row-blocks 60 | user_content = req["messages"][1]["content"] 61 | # We expect 2 text blocks since both rows had content 62 | assert len(user_content) == 2 63 | 64 | def test_create_tasks_unlimited(discover_skill_unlimited): 65 | """ 66 | label_count=-1 => system prompt includes 'You may select any number of labels.' 67 | """ 68 | data = [ 69 | {"col1": "rowA"}, 70 | {"col1": "rowB"}, 71 | {"col1": "rowC"} 72 | ] 73 | tasks = discover_skill_unlimited.create_tasks(data) 74 | assert len(tasks) == 1 75 | 76 | req = tasks[0]["request"] 77 | assert "any number of labels" in req["messages"][0]["content"] 78 | 79 | def test_create_tasks_empty_df(discover_skill_limited): 80 | """ 81 | If no blocks are created (all empty?), return []. 82 | """ 83 | data = [ 84 | {"col1": ""}, 85 | {"col1": ""} 86 | ] 87 | tasks = discover_skill_limited.create_tasks(data) 88 | assert tasks == [], "No content => no tasks generated." 89 | 90 | def test_build_function_def(discover_skill_limited): 91 | """ 92 | Ensure the function definition is structured as expected (top-level 'type' 93 | and 'function', with parameters => type=array). 94 | """ 95 | func_def = discover_skill_limited._build_function_def() 96 | assert func_def["type"] == "function" 97 | 98 | f_def = func_def["function"] 99 | assert f_def["name"] == "infer_labels" 100 | assert "parameters" in f_def 101 | 102 | props = f_def["parameters"]["properties"] 103 | assert "labels" in props 104 | 105 | labels_prop = props["labels"] 106 | assert labels_prop["type"] == "array" 107 | assert labels_prop["items"]["type"] == "string" 108 | 109 | def test_parse_result_none(discover_skill_limited): 110 | """ 111 | parse_function_call returns None => parse_result => []. 112 | """ 113 | discover_skill_limited.parse_function_call = MagicMock(return_value=None) 114 | result = discover_skill_limited.parse_result({}) 115 | assert result == [], "If parse_function_call is None => empty list" 116 | 117 | def test_parse_result_list(discover_skill_limited): 118 | """ 119 | parse_function_call returns a list => parse_result returns the list as-is. 120 | """ 121 | discover_skill_limited.parse_function_call = MagicMock(return_value=["label1", "label2"]) 122 | result = discover_skill_limited.parse_result({}) 123 | assert result == ["label1", "label2"] -------------------------------------------------------------------------------- /flashlearn/skills/tests/test_general_skill.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from unittest.mock import MagicMock 3 | 4 | from flashlearn.skills import GeneralSkill 5 | 6 | 7 | @pytest.fixture 8 | def function_def(): 9 | """ 10 | A minimal custom function definition used to initialize GeneralSkill. 11 | """ 12 | return { 13 | "type": "function", 14 | "function": { 15 | "name": "custom_func", 16 | "description": "A custom function definition for testing", 17 | "strict": True, 18 | "parameters": { 19 | "type": "object", 20 | "properties": { 21 | "some_field": {"type": "string"} 22 | }, 23 | "required": ["some_field"], 24 | "additionalProperties": False 25 | } 26 | } 27 | } 28 | 29 | 30 | @pytest.fixture 31 | def general_skill(function_def): 32 | """ 33 | Instantiate a GeneralSkill with some minimal parameters. 34 | We specify self.columns = ["col1", "col2"], which the skill 35 | can use if no columns are provided at task-creation time. 36 | """ 37 | return GeneralSkill( 38 | model_name="gpt-4", 39 | function_definition=function_def, 40 | system_prompt="Custom system prompt", 41 | columns=["col1", "col2"] 42 | ) 43 | 44 | 45 | def test_instantiate_general_skill(general_skill, function_def): 46 | """ 47 | Tests constructor fields. 48 | """ 49 | assert general_skill.model_name == "gpt-4" 50 | assert general_skill.system_prompt == "Custom system prompt" 51 | assert general_skill._function_definition == function_def 52 | assert general_skill.columns == ["col1", "col2"] 53 | 54 | 55 | def test_build_function_def(general_skill, function_def): 56 | """ 57 | _build_function_def should return exactly what we passed in. 58 | """ 59 | output = general_skill._build_function_def() 60 | assert output == function_def 61 | 62 | 63 | def test_parse_result(general_skill): 64 | """ 65 | parse_result just returns the raw_result as-is (no transformation). 66 | """ 67 | raw = {"some": "stuff"} 68 | parsed = general_skill.parse_result(raw) 69 | assert parsed is raw 70 | 71 | 72 | def test_create_tasks_using_self_columns(general_skill): 73 | """ 74 | If create_tasks is called with columns=None, 75 | we fall back to self.columns from the constructor. 76 | We provide data with more keys than in self.columns, 77 | ensuring only the configured columns are actually used. 78 | """ 79 | data = [ 80 | {"col1": "row1", "col2": "x", "col3": "extra"}, 81 | {"col1": "row2", "col2": "y", "col3": "ignored"} 82 | ] 83 | tasks = general_skill.create_tasks(data) # columns=None → fallback to ["col1", "col2"] 84 | # Should produce 2 tasks (one per dict entry) 85 | assert len(tasks) == 2 86 | 87 | for i, task in enumerate(tasks): 88 | assert task["custom_id"] == str(i) 89 | user_msg = task["request"]["messages"][1] 90 | # The user_msg content should only reflect col1 and col2, ignoring col3 91 | content_blocks = user_msg["content"] 92 | # Expect 2 blocks: one for col1, one for col2 93 | assert len(content_blocks) == 3 94 | 95 | # Check the block texts in order 96 | block_texts = [b.get("text") for b in content_blocks if b["type"] == "text"] 97 | assert block_texts == [data[i]["col1"], data[i]["col2"], data[i]["col3"]] 98 | 99 | 100 | def test_create_tasks_inherits_behavior_empty_rows(general_skill): 101 | """ 102 | If a row is empty in the chosen columns, that row is skipped. 103 | A row is "empty" if all of the chosen columns are empty strings. 104 | """ 105 | data = [ 106 | {"col1": "some text", "col2": "val"}, 107 | {"col1": "", "col2": ""} 108 | ] 109 | tasks = general_skill.create_tasks(data, columns=["col1", "col2"]) 110 | # Only the first entry yields content, second is empty => skip 111 | assert len(tasks) == 1 112 | assert tasks[0]["custom_id"] == "0" 113 | 114 | 115 | def test_load_skill(function_def): 116 | """ 117 | Covers load_skill, ensuring it returns an initialized GeneralSkill 118 | with config-specified fields. 119 | """ 120 | config = { 121 | "skill_class": "GeneralSkill", 122 | "system_prompt": "Loaded skill sys prompt", 123 | "function_definition": function_def, 124 | "columns": ["my_col", "another_col"] 125 | } 126 | from flashlearn.skills.general_skill import GeneralSkill 127 | skill_instance = GeneralSkill.load_skill(config,model_name="gpt-4o",) 128 | 129 | assert isinstance(skill_instance, GeneralSkill) 130 | assert skill_instance.model_name == "gpt-4o" 131 | assert skill_instance.system_prompt == "Loaded skill sys prompt" 132 | assert skill_instance._function_definition == function_def 133 | assert skill_instance.columns == ["my_col", "another_col"] -------------------------------------------------------------------------------- /flashlearn/skills/toolkit/generate_init.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | 5 | def add_json_to_init(folder_path): 6 | init_file_path = os.path.join(folder_path, "__init__.py") 7 | 8 | with open(init_file_path, 'w', encoding='utf-8') as init_file: 9 | init_file.write("# Auto-generated Python dictionaries from JSON files\n\n") 10 | 11 | for filename in os.listdir(folder_path): 12 | if filename.endswith(".json"): 13 | json_path = os.path.join(folder_path, filename) 14 | try: 15 | # Load JSON data 16 | with open(json_path, 'r', encoding='utf-8') as json_file: 17 | data = json.load(json_file) 18 | 19 | # Convert JSON file name to valid Python variable name 20 | variable_name = filename.replace(".json", "").replace("-", "_") 21 | 22 | # Write Python-friendly dictionary 23 | init_file.write(f"{variable_name} = {repr(data)}\n\n") 24 | except (json.JSONDecodeError, IOError) as e: 25 | print(f"Error processing {filename}: {e}") 26 | 27 | 28 | # Run the function in the current directory 29 | add_json_to_init(os.getcwd()) 30 | -------------------------------------------------------------------------------- /flashlearn/skills/toolkit/simple_search.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | class SimpleGoogleSearch: 5 | def __init__(self, api_key, cse_id): 6 | self.api_key = api_key 7 | self.cse_id = cse_id 8 | def search(self, queries): 9 | r = [] 10 | for q in queries: 11 | p = {"key": self.api_key, "cx": self.cse_id, "q": q} 12 | r.append(requests.get("https://www.googleapis.com/customsearch/v1", params=p).json()) 13 | return r 14 | -------------------------------------------------------------------------------- /flashlearn/skills/toolkit/validate_json_files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | 5 | def validate_json_files(): 6 | # Get the current working directory 7 | folder_path = os.getcwd() 8 | 9 | for filename in os.listdir(folder_path): 10 | if filename.endswith(".json"): 11 | file_path = os.path.join(folder_path, filename) 12 | 13 | try: 14 | # Load JSON file 15 | with open(file_path, 'r', encoding='utf-8') as file: 16 | data = json.load(file) 17 | 18 | # Check if 'function_definition' is a dictionary 19 | if not isinstance(data.get("function_definition"), dict): 20 | print(f"Deleting {filename}: 'function_definition' is not a dictionary") 21 | os.remove(file_path) 22 | else: 23 | print(f"Validated {filename}: 'function_definition' is a dictionary") 24 | except (json.JSONDecodeError, IOError) as e: 25 | print(f"Error processing {filename}: {e}") 26 | # Optionally delete invalid JSON files 27 | os.remove(file_path) 28 | 29 | 30 | # Run the validation function 31 | validate_json_files() 32 | -------------------------------------------------------------------------------- /flashlearn/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Initialize the 'utils' subpackage of FlashLearn. 3 | 4 | This file ensures 'utils' is recognized as a subpackage. 5 | You can expose utility classes/functions here if needed. 6 | """ 7 | 8 | # Example of importing utility modules or classes: 9 | from .demo_data import imdb_reviews_50k, cats_and_dogs 10 | from .logging_utils import setup_logger 11 | from .token_utils import count_tokens_for_tasks 12 | 13 | __all__ = [ 14 | 'imdb_reviews_50k', 'cats_and_dogs', 15 | 'setup_logger', 16 | 'count_tokens_for_tasks', 17 | ] -------------------------------------------------------------------------------- /flashlearn/utils/demo_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import base64 4 | import csv 5 | from io import BytesIO 6 | 7 | import kagglehub 8 | from PIL import Image 9 | 10 | 11 | def encode_image_to_base64(image_path: str) -> str: 12 | """ 13 | Encodes an image to a base64 JPEG string. 14 | :param image_path: File path of the image 15 | :return: Base64 encoded string of the image 16 | """ 17 | with Image.open(image_path) as img: 18 | buffered = BytesIO() 19 | img.save(buffered, format="JPEG") 20 | return base64.b64encode(buffered.getvalue()).decode("utf-8") 21 | 22 | 23 | def cats_and_dogs(sample: int = 100, train_ratio: float = 0.5): 24 | """ 25 | Returns two lists of dictionaries (train_data, test_data) with base64-encoded 26 | images of cats and dogs. Each dictionary has: 27 | { 28 | "image_base64": , 29 | "label": 0 or 1 30 | } 31 | The first list is the 'train' portion, and the second is 'test'. 32 | 33 | :param sample: Total number of images to sample from the dataset. 34 | :param train_ratio: Fraction of data to put in the training set. 35 | :return: (train_data, test_data) 36 | """ 37 | # Download and locate the dataset 38 | path = kagglehub.dataset_download("samuelcortinhas/cats-and-dogs-image-classification") 39 | cats_dir = os.path.join(path, "train", "cats") 40 | dogs_dir = os.path.join(path, "train", "dogs") 41 | 42 | # Gather and sample file paths 43 | cat_images = [os.path.join(cats_dir, f) for f in os.listdir(cats_dir) 44 | if f.lower().endswith((".jpg", ".jpeg"))] 45 | dog_images = [os.path.join(dogs_dir, f) for f in os.listdir(dogs_dir) 46 | if f.lower().endswith((".jpg", ".jpeg"))] 47 | 48 | # Sample equally from cats and dogs 49 | half_sample = sample // 2 50 | cat_sample = random.sample(cat_images, half_sample) 51 | dog_sample = random.sample(dog_images, half_sample) 52 | 53 | # Encode images and build list of dicts 54 | data = [] 55 | for img_path in cat_sample: 56 | encoded_image = encode_image_to_base64(img_path) 57 | data.append({"image_base64": encoded_image, "label": 0}) 58 | for img_path in dog_sample: 59 | encoded_image = encode_image_to_base64(img_path) 60 | data.append({"image_base64": encoded_image, "label": 1}) 61 | 62 | return data 63 | 64 | 65 | def imdb_reviews_50k(sample: int = 100, full=False): 66 | """ 67 | Returns IMDb reviews as two lists of dicts (train_data, test_data). 68 | Each dict has: 69 | { 70 | "review": , 71 | "sentiment": <"positive" or "negative"> 72 | } 73 | 74 | The first list is the 'train' portion (10% by default), 75 | the second list is the 'test' portion. 76 | 77 | :param sample: Number of total samples to extract from the dataset. 78 | :return: (train_data, test_data) 79 | """ 80 | # Download and load the IMDb dataset (CSV) 81 | dataset_path = kagglehub.dataset_download("lakshmi25npathi/imdb-dataset-of-50k-movie-reviews") 82 | csv_path = os.path.join(dataset_path, "IMDB Dataset.csv") 83 | 84 | # Read CSV as list of dicts 85 | all_rows = [] 86 | with open(csv_path, "r", encoding="utf-8") as f: 87 | reader = csv.DictReader(f) # fieldnames from header: "review","sentiment" 88 | for row in reader: 89 | # row is {"review": ..., "sentiment": ...} 90 | all_rows.append(row) 91 | 92 | if not full: 93 | # Sample a random subset if needed 94 | if sample < len(all_rows): 95 | all_rows = random.sample(all_rows, sample) 96 | 97 | # Shuffle the data 98 | random.shuffle(all_rows) 99 | 100 | return all_rows -------------------------------------------------------------------------------- /flashlearn/utils/logging_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | def setup_logger(name="FlashLearn", level=logging.ERROR): 5 | """ 6 | Sets up a logger with the specified name and level 7 | and also configures openai, httpx, and httpcore loggers to ERROR. 8 | """ 9 | logger = logging.getLogger(name) 10 | logger.setLevel(level) 11 | 12 | # Silence 3rd-party libraries 13 | logging.getLogger("openai").setLevel(logging.ERROR) 14 | logging.getLogger("httpx").setLevel(logging.ERROR) 15 | logging.getLogger("httpcore").setLevel(logging.ERROR) 16 | 17 | # Only add a StreamHandler if none exist 18 | if not logger.hasHandlers(): 19 | console_handler = logging.StreamHandler(sys.stdout) 20 | console_handler.setLevel(level) 21 | formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s") 22 | console_handler.setFormatter(formatter) 23 | logger.addHandler(console_handler) 24 | 25 | return logger -------------------------------------------------------------------------------- /flashlearn/utils/tests/test_demo_data.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from flashlearn.utils.demo_data import cats_and_dogs, imdb_reviews_50k 4 | 5 | 6 | @pytest.mark.parametrize("sample,train_ratio", [ 7 | (10, 0.5), 8 | (20, 0.2), 9 | ]) 10 | def test_cats_and_dogs(sample, train_ratio): 11 | """ 12 | Test that cats_and_dogs runs end-to-end, downloads the dataset, 13 | and returns lists of dictionaries with the correct keys. 14 | """ 15 | test_data = cats_and_dogs(sample=sample, train_ratio=train_ratio) 16 | 17 | # Check type 18 | assert isinstance(test_data, list), "test_data should be a list." 19 | 20 | # Check lengths roughly match the ratio 21 | total_len = len(test_data) 22 | assert total_len == sample, f"Total returned items must be equal to sample={sample}" 23 | 24 | # Check at least one item if sample > 0 25 | if sample > 0: 26 | assert len(test_data) > 0, "Expected some testing data." 27 | 28 | # Check structure of first item 29 | first_item = test_data[0] 30 | assert "image_base64" in first_item, "Each item should have 'image_base64'." 31 | assert "label" in first_item, "Each item should have 'label'." 32 | assert isinstance(first_item["image_base64"], str), "'image_base64' must be a string." 33 | assert isinstance(first_item["label"], int), "'label' must be an integer." 34 | 35 | 36 | @pytest.mark.parametrize("sample", [10, 20]) 37 | def test_imdb_reviews_50k(sample): 38 | """ 39 | Test that imdb_reviews_50k runs end-to-end, downloads the IMDb dataset, 40 | randomly samples the requested number of rows, and splits them. 41 | """ 42 | test_data = imdb_reviews_50k(sample=sample) 43 | 44 | # Basic checks 45 | assert isinstance(test_data, list), "test_data should be a list." 46 | total_len = len(test_data) 47 | assert total_len == sample, f"Total returned items must match requested sample={sample}." 48 | 49 | # Check keys 50 | if sample > 0: 51 | first_item = test_data[0] 52 | assert "review" in first_item, "Should contain a 'review' field." 53 | assert "sentiment" in first_item, "Should contain a 'sentiment' field." 54 | assert isinstance(first_item["review"], str), "'review' must be a string." 55 | assert isinstance(first_item["sentiment"], str), "'sentiment' must be a string." -------------------------------------------------------------------------------- /flashlearn/utils/tests/test_token_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from flashlearn.utils.token_utils import _count_tokens_for_messages, _count_tokens_for_function_defs, \ 4 | count_tokens_for_task, count_tokens_for_tasks 5 | 6 | 7 | @pytest.mark.parametrize("messages,expected_min_tokens", [ 8 | ( 9 | [ 10 | {"role": "system", "content": "Hello, I am the system message."}, 11 | {"role": "user", "content": "Tell me a story please."} 12 | ], 13 | 1 14 | ), 15 | ( 16 | [ 17 | {"role": "user", "content": "Short prompt."} 18 | ], 19 | 1 20 | ), 21 | ([], 0), 22 | ]) 23 | def test_count_tokens_for_messages(messages, expected_min_tokens): 24 | """ 25 | Basic tests for _count_tokens_for_messages using a known model name. 26 | We check that the token count is at least the expected_min_tokens. 27 | """ 28 | model_name = "gpt-3.5-turbo" 29 | tokens = _count_tokens_for_messages(messages, model_name) 30 | # We primarily verify that the function doesn't crash and returns a numeric token count. 31 | assert isinstance(tokens, int) 32 | assert tokens >= expected_min_tokens 33 | 34 | 35 | @pytest.mark.parametrize("function_defs,expected_min_tokens", [ 36 | ( 37 | [ 38 | { 39 | "name": "test_function", 40 | "description": "A simple test function definition", 41 | "parameters": {"type": "object", "properties": {"test_prop": {"type": "string"}}} 42 | } 43 | ], 44 | 1 45 | ), 46 | ([], 0), 47 | ( 48 | [ 49 | {"arbitrary": "Anything could be here."}, 50 | {"second_def": 123} 51 | ], 52 | 1 53 | ), 54 | ]) 55 | def test_count_tokens_for_function_defs(function_defs, expected_min_tokens): 56 | """ 57 | Check that _count_tokens_for_function_defs produces a non-negative 58 | integer token count, and is at least expected_min_tokens. 59 | """ 60 | model_name = "gpt-3.5-turbo" 61 | tokens = _count_tokens_for_function_defs(function_defs, model_name) 62 | assert isinstance(tokens, int) 63 | assert tokens >= expected_min_tokens 64 | 65 | 66 | def test_count_tokens_for_task_basic(): 67 | """ 68 | Verify count_tokens_for_task sums tokens from both messages and functions. 69 | """ 70 | task = { 71 | "custom_id": "123", 72 | "request": { 73 | "model": "gpt-3.5-turbo", 74 | "messages": [ 75 | {"role": "system", "content": "System says hello."}, 76 | {"role": "user", "content": "User asks a question."} 77 | ], 78 | "functions": [ 79 | {"name": "dummy_function", "description": "Does something."} 80 | ] 81 | } 82 | } 83 | tokens = count_tokens_for_task(task, default_model="gpt-3.5-turbo") 84 | assert isinstance(tokens, int) 85 | assert tokens > 0, "Should count some tokens for both messages and function definitions." 86 | 87 | 88 | def test_count_tokens_for_task_no_request_key(): 89 | """ 90 | If 'request' key is missing or empty, we should handle gracefully and get zero tokens. 91 | """ 92 | task = { 93 | "custom_id": "no_request" 94 | } 95 | tokens = count_tokens_for_task(task, default_model="gpt-3.5-turbo") 96 | assert tokens == 0 97 | 98 | 99 | def test_count_tokens_for_tasks_basic(): 100 | """ 101 | Confirm that count_tokens_for_tasks sums the token counts across multiple tasks. 102 | """ 103 | tasks = [ 104 | { 105 | "custom_id": "1", 106 | "request": { 107 | "model": "gpt-3.5-turbo", 108 | "messages": [{"role": "user", "content": "Hello world"}], 109 | "functions": [] 110 | } 111 | }, 112 | { 113 | "custom_id": "2", 114 | "request": { 115 | "model": "gpt-3.5-turbo", 116 | "messages": [{"role": "assistant", "content": "Sure, here's a reply."}], 117 | "functions": [{"fun": "some definition"}] 118 | } 119 | } 120 | ] 121 | total_tokens = count_tokens_for_tasks(tasks, default_model="gpt-3.5-turbo") 122 | assert isinstance(total_tokens, int) 123 | # We can't predict exact tokens, but it should be > 0 if there's content in both tasks. 124 | assert total_tokens > 0 125 | 126 | 127 | def test_count_tokens_for_tasks_empty(): 128 | """ 129 | If tasks is an empty list, total tokens should be 0. 130 | """ 131 | empty_tasks = [] 132 | total_tokens = count_tokens_for_tasks(empty_tasks, default_model="gpt-3.5-turbo") 133 | assert total_tokens == 0 -------------------------------------------------------------------------------- /flashlearn/utils/token_utils.py: -------------------------------------------------------------------------------- 1 | import tiktoken 2 | from typing import List, Dict, Any 3 | 4 | def _count_tokens_for_messages(messages: List[Dict[str, str]], model_name: str) -> int: 5 | """ 6 | Count tokens for the conversation messages using tiktoken. 7 | Each item in `messages` is { "role": <>, "content": <> }. 8 | """ 9 | enc = tiktoken.encoding_for_model(model_name) 10 | text = "" 11 | for msg in messages: 12 | role = msg.get("role", "") 13 | content = msg.get("content", "") 14 | text += f"{role}: {content}\n" 15 | return len(enc.encode(text)) 16 | 17 | def _count_tokens_for_function_defs(function_defs: List[Dict[str, Any]], model_name: str) -> int: 18 | """ 19 | Approximate tokens for function definitions by converting them to a string 20 | (str() or JSON) and encoding that. 21 | """ 22 | enc = tiktoken.encoding_for_model(model_name) 23 | total_tokens = 0 24 | for f_def in function_defs: 25 | serialized = str(f_def) 26 | total_tokens += len(enc.encode(serialized)) 27 | return total_tokens 28 | 29 | def count_tokens_for_task(task: Dict[str, Any], default_model: str) -> int: 30 | """ 31 | Given a single task dict with structure: 32 | { 33 | "custom_id": <>, 34 | "request": { 35 | "model": , 36 | "messages": [...], 37 | "functions": [...], 38 | ... 39 | } 40 | } 41 | count the tokens from messages and function definitions. 42 | """ 43 | req_data = task.get("request", {}) 44 | model_name = req_data.get("model", default_model) 45 | messages = req_data.get("messages", []) 46 | functions = req_data.get("functions", []) 47 | 48 | tokens_messages = _count_tokens_for_messages(messages, model_name) 49 | tokens_funcs = _count_tokens_for_function_defs(functions, model_name) 50 | return tokens_messages + tokens_funcs 51 | 52 | def count_tokens_for_tasks(tasks: List[Dict[str, Any]], default_model: str) -> int: 53 | """ 54 | Sum token count across every task. 55 | """ 56 | total = 0 57 | for t in tasks: 58 | total += count_tokens_for_task(t, default_model) 59 | return total -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=61.0", 4 | "wheel>=0.37.0" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | 8 | [project] 9 | name = "flashlearn" 10 | version = "1.0.4" 11 | description = "Agents made simple" 12 | readme = "README.md" # Points to your README file 13 | authors = [ 14 | {name = "Gal Giacomelli", email = "info@clerkly.co"} 15 | ] 16 | requires-python = ">=3.8" 17 | 18 | # List your normal or "weakly pinned" dependencies here: 19 | dependencies = [ 20 | "aiohappyeyeballs>=2.4.4", 21 | "pillow>=11.1.0", 22 | "aiohttp>=3.11.11", 23 | "aiosignal>=1.3.2", 24 | "annotated-types>=0.7.0", 25 | "anyio>=4.8.0", 26 | "attrs>=24.3.0", 27 | "certifi>=2024.12.14", 28 | "charset-normalizer>=3.4.1", 29 | "click>=8.1.8", 30 | "colorama>=0.4.6", 31 | "distro>=1.9.0", 32 | "filelock>=3.17.0", 33 | "frozenlist>=1.5.0", 34 | "fsspec>=2024.12.0", 35 | "h11>=0.14.0", 36 | "httpcore>=1.0.7", 37 | "httpx>=0.27.2", 38 | "huggingface-hub>=0.27.1", 39 | "idna>=3.10", 40 | "importlib_metadata>=8.6.1", 41 | "Jinja2>=3.1.5", 42 | "jiter>=0.8.2", 43 | "joblib>=1.4.2", 44 | "jsonschema>=4.23.0", 45 | "jsonschema-specifications>=2024.10.1", 46 | "kagglehub>=0.3.6", 47 | "litellm>=1.59.3", 48 | "MarkupSafe>=3.0.2", 49 | "multidict>=6.1.0", 50 | "openai>=1.60.0", 51 | "packaging>=24.2", 52 | "propcache>=0.2.1", 53 | "pydantic>=2.10.5", 54 | "pydantic_core>=2.27.2", 55 | "python-dateutil>=2.9.0.post0", 56 | "python-dotenv>=1.0.1", 57 | "pytz>=2024.2", 58 | "PyYAML>=6.0.2", 59 | "referencing>=0.36.1", 60 | "regex>=2024.11.6", 61 | "requests>=2.32.3", 62 | "rpds-py>=0.22.3", 63 | "six>=1.17.0", 64 | "sniffio>=1.3.1", 65 | "threadpoolctl>=3.5.0", 66 | "tiktoken>=0.8.0", 67 | "tokenizers>=0.21.0", 68 | "tqdm>=4.67.1", 69 | "typing_extensions>=4.12.2", 70 | "tzdata>=2025.1", 71 | "urllib3>=2.3.0", 72 | "yarl>=1.18.3", 73 | "zipp>=3.21.0" 74 | ] -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pravko-Solutions/FlashLearn/b48e893b543e5360e3069be91c3556a7f9c5c7d1/requirements.txt --------------------------------------------------------------------------------