├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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
--------------------------------------------------------------------------------