diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3212c7f..975fab6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,11 +47,10 @@ jobs: run: uv sync --dev - name: Tests with coverage run: | - uv run pytest --cov=intent_kit --cov-report=xml + uv run pytest --cov=intent_kit --cov-branch --cov-report=xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: - files: ./coverage.xml token: ${{ secrets.CODECOV_TOKEN }} eval: diff --git a/.gitignore b/.gitignore index 1e7d110..4f7dea7 100644 --- a/.gitignore +++ b/.gitignore @@ -53,5 +53,5 @@ dist/ build/ .env - +coverage.xml repomix-output.* \ No newline at end of file diff --git a/README.md b/README.md index 88e16a5..ab594c6 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![CI](https://github.com/Stephen-Collins-tech/intent-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/Stephen-Collins-tech/intent-kit/actions/workflows/ci.yml) [![Coverage Status](https://codecov.io/gh/Stephen-Collins-tech/intent-kit/branch/main/graph/badge.svg)](https://codecov.io/gh/Stephen-Collins-tech/intent-kit) [![Documentation](https://img.shields.io/badge/docs-online-blue)](https://docs.intentkit.io) -[![PyPI](https://img.shields.io/pypi/v/intent-kit)](https://pypi.org/project/intent-kit) +[![PyPI](https://img.shields.io/pypi/v/intentkit-py)](https://pypi.org/project/intentkit-py) [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/Stephen-Collins-tech/intent-kit/HEAD?filepath=examples%2Fsimple_demo.ipynb) A Python library for building hierarchical intent classification and execution systems with support for multiple AI service backends. @@ -63,11 +63,11 @@ intent-kit is designed as a **universal intent framework** that works with any c ### **Start Simple (Zero Dependencies)** ```python -from intent_kit import create_intent_handler, keyword_classifier, ClassifierNode +from intent_kit import handler, keyword_classifier, ClassifierNode # Pure rule-based classification - no external dependencies intent_handlers = [ - create_intent_handler( + handler( name="greet", description="Greet user", handler_func=lambda name: f"Hello {name}!", @@ -101,9 +101,9 @@ classifier = ClassifierNode( ### **Go AI (Optional Enhancement)** ```python # Add AI capabilities when needed -from intent_kit import create_llm_classifier +from intent_kit import llm_classifier -classifier = create_llm_classifier( +classifier = llm_classifier( name="root", children=intent_handlers, llm_config=LLM_CONFIG # Optional AI enhancement @@ -168,32 +168,30 @@ pip install 'intent-kit[viz]' ## Quick Start -### New API (Recommended) - -The new API provides a simplified, declarative way to build intent graphs with automatic argument extraction and LLM integration: +The API provides a simplified, declarative way to build intent graphs with automatic argument extraction and LLM integration: ```python -from intent_kit import IntentGraphBuilder, create_intent_handler, create_llm_classifier +from intent_kit import IntentGraphBuilder, handler, llm_classifier from intent_kit.context import IntentContext # Create intent handlers with automatic argument extraction -greet_handler = create_intent_handler( +greet_handler = handler( name="greet", description="Greet the user", - handler_func=lambda name: f"Hello {name}!", + handler_func=lambda name, **kwargs: f"Hello {name}!", param_schema={"name": str} # No llm_config = uses rule-based extraction ) -weather_handler = create_intent_handler( +weather_handler = handler( name="weather", description="Get weather information for a location", - handler_func=lambda location: f"The weather in {location} is sunny.", + handler_func=lambda location, **kwargs: f"The weather in {location} is sunny.", param_schema={"location": str} ) # Create classifier with auto-wired children descriptions -classifier = create_llm_classifier( +classifier = llm_classifier( name="root", children=[greet_handler, weather_handler], llm_config=LLM_CONFIG, # Optional: enables LLM-powered classification @@ -213,71 +211,6 @@ result = graph.route("Hello Alice", context=context) print(result.output) # "Hello Alice!" ``` -### Legacy API Example - -```python -from intent_kit.builder import handler -from intent_kit.classifiers import keyword_classifier -from intent_kit.context import IntentContext - -import re - -# Define argument extractors -def extract_weather_args(user_input: str) -> dict: - """Extract city from user input.""" - match = re.search(r'weather (?:for|in) (\w+)', user_input, re.IGNORECASE) - return {"city": match.group(1) if match else "Unknown"} - -def extract_greeting_args(user_input: str) -> dict: - """Extract person name from user input.""" - match = re.search(r'hello (\w+)', user_input, re.IGNORECASE) - return {"person": match.group(1) if match else "there"} - -# Define handlers -def handle_weather(city: str, context: IntentContext) -> str: - return f"The weather in {city} is sunny." - -def handle_greeting(person: str, context: IntentContext) -> str: - greeting_count = context.get("greeting_count", 0) + 1 - context.set("greeting_count", greeting_count, modified_by="greet") - return f"Hello, {person}! (Greeting #{greeting_count})" - -# Create intent nodes -weather_node = handler( - name="Weather", - description="Get weather information for a city", - handler_func=handle_weather, - param_schema={"city": str} -) - -greeting_node = handler( - name="Greeting", - description="Send a greeting to someone", - handler_func=handle_greeting, - param_schema={"person": str}, - context_outputs={"greeting_count"} -) - -# Create classifier node -from intent_kit.classifiers import ClassifierNode -root_node = ClassifierNode( - name="Root", - classifier=keyword_classifier, - children=[weather_node, greeting_node], - description="Main intent classifier" -) - -# Set parent references -weather_node.parent = root_node -greeting_node.parent = root_node - -# Execute intents with context -context = IntentContext(session_id="user_123") -result = root_node.execute("What's the weather for Paris?", context=context) -print(result.output) # Shows the classifier's routing result -print(result.children_results[0].output) # Shows the actual intent output: "The weather in Paris is sunny." -``` - ### Advanced Example with IntentGraph ```python @@ -348,7 +281,7 @@ llm_config = { weather_handler = handler( name="weather", description="Get weather information for a location", - handler_func=lambda city: f"The weather in {city} is sunny.", + handler_func=lambda city, **kwargs: f"The weather in {city} is sunny.", param_schema={"city": str}, llm_config=llm_config # Enables LLM-based argument extraction ) @@ -356,7 +289,7 @@ weather_handler = handler( greet_handler = handler( name="greet", description="Send a greeting to someone", - handler_func=lambda name: f"Hello {name}!", + handler_func=lambda name, **kwargs: f"Hello {name}!", param_schema={"name": str}, llm_config=llm_config ) @@ -408,7 +341,7 @@ name = context.get("user_name", "Unknown") count = context.get("greeting_count", 0) # Track dependencies in intent nodes -weather_node = TreeBuilder.handler_node( +weather_node = handler( name="Weather", param_schema={"city": str}, handler=handle_weather, @@ -419,9 +352,9 @@ weather_node = TreeBuilder.handler_node( ) ``` -### New Builder API (Recommended) +### Builder API -The new API provides a simplified, declarative way to build intent graphs: +The API provides a simplified, declarative way to build intent graphs: #### handler() @@ -431,12 +364,12 @@ Creates a handler node with automatic argument extraction: from intent_kit import handler greet_handler = handler( - name="greet", - description="Greet the user", - handler_func=lambda name: f"Hello {name}!", - param_schema={"name": str}, - llm_config=LLM_CONFIG # Optional: enables LLM-based argument extraction -) + name="greet", + description="Greet the user", + handler_func=lambda name, **kwargs: f"Hello {name}!", + param_schema={"name": str}, + llm_config=LLM_CONFIG # Optional: enables LLM-based argument extraction + ) ``` #### llm_classifier() @@ -521,7 +454,7 @@ classifier_node = ClassifierNode( ### Argument Extraction -The new API provides automatic argument extraction with two modes: +The API provides automatic argument extraction with two modes: #### LLM-based Extraction @@ -612,7 +545,7 @@ llm_client = LLMFactory.create_client({ # Available providers: openai, anthropic, google, ollama ``` -### Benefits of the New API +### Benefits of the API 1. **Simplified Syntax**: Less boilerplate code required 2. **Automatic Argument Extraction**: No need to manually create argument extractors @@ -621,64 +554,7 @@ llm_client = LLMFactory.create_client({ 5. **Fallback Support**: Rule-based extraction when LLM config is not available 6. **Backwards Compatibility**: Original API still works for advanced use cases -### Migration from Legacy API - -#### Before (Legacy API) -```python -from intent_kit.classifiers.llm_classifier import create_llm_classifier, create_llm_arg_extractor - -# Create argument extractor -arg_extractor = create_llm_arg_extractor(LLM_CONFIG, extraction_prompt, param_schema) - -# Create handler -greet_handler = HandlerNode( - name="greet", - param_schema={"name": str}, - handler=lambda name: f"Hello {name}!", - arg_extractor=arg_extractor, - description="Greet the user" -) - -# Create classifier with manual descriptions -classifier = ClassifierNode( - name="root", - classifier=create_llm_classifier(llm_config, prompt, descriptions), - children=[greet_handler], - description="Main classifier" -) - -# Create graph -graph = IntentGraph() -graph.add_root_node(classifier) -``` - -#### After (New API) -```python -from intent_kit import handler, llm_classifier, IntentGraphBuilder - -# Create handler with automatic argument extraction -greet_handler = handler( - name="greet", - description="Greet the user", - handler_func=lambda name: f"Hello {name}!", - param_schema={"name": str}, - llm_config=LLM_CONFIG -) -# Create classifier with auto-wired descriptions -classifier = llm_classifier( - name="root", - children=[greet_handler], - llm_config=LLM_CONFIG -) - -# Build graph using builder pattern -graph = ( - IntentGraphBuilder() - .root(classifier) - .build() -) -``` ### IntentGraph - Multi-Intent Routing @@ -748,14 +624,43 @@ Graphs are saved to `intentkit_graphs/` directory with unique filenames based on ## Examples -See the `examples/` directory for complete working examples: +The `examples/` directory contains comprehensive demonstrations of IntentKit functionality. Each example is designed to be minimal and focused on specific features. + +### Available Examples + +#### Simple Demo (`simple_demo.py`) +A basic demonstration of IntentKit with LLM-powered intent classification and argument extraction. Shows the core IntentGraph functionality with a **pass-through splitter** (default behavior). + +#### Multi-Intent Demo (`multi_intent_demo.py`) +A demonstration of multi-intent handling using the rule-based splitter. Shows how to handle complex inputs like "Hello Alice and what's the weather in San Francisco". -* **`simple_demo.py`** - Basic IntentGraph with LLM integration using the new API -* **`context_demo.py`** - Complete context-aware workflow example -* **`ollama_demo.py`** - Using local Ollama models for offline processing -* **`error_demo.py`** - Error handling demo with the new API -* **`validation_demo.py`** - Graph validation and structure analysis -* **`splitter_demo.py`** - Multi-intent handling with splitter nodes +#### Error Demo (`error_demo.py`) +A demonstration of error handling and debugging features. Shows how to handle various error scenarios and debug intent routing issues. + +#### Context Demo (`context_demo.py`) +A demonstration of context and dependency management. Shows how handlers can read from and write to shared context. + +#### Context Debugging Demo (`context_debug_demo.py`) +A comprehensive demonstration of context debugging features including: +- `debug_context` and `context_trace` parameters +- Dependency mapping and analysis with `get_context_dependencies()` +- Context flow validation with `validate_context_flow()` +- Debug output formats (console, JSON) with `trace_context_execution()` + +#### Ollama Demo (`ollama_demo.py`) +A demonstration of using IntentKit with local Ollama models. Shows how to configure and use local LLM models. + +### Default Behavior + +By default, IntentKit uses a **pass-through splitter** that doesn't split user input. This is the safest approach for most use cases, as it avoids accidentally splitting inputs like "What's 15 plus 7?" on mathematical operators. + +If you need multi-intent handling, explicitly configure the rule-based splitter: + +```python +from intent_kit.splitters import rule_splitter + +return IntentGraphBuilder().root(classifier).splitter(rule_splitter).build() +``` ### Running Examples @@ -772,11 +677,95 @@ python examples/context_demo.py # Error Demo python examples/error_demo.py -# Validation Demo -python examples/validation_demo.py +# Multi-Intent Demo +python examples/multi_intent_demo.py + +# Context Debug Demo +python examples/context_debug_demo.py +``` + +### Setup Requirements + +#### API Keys for LLM Services (Optional) + +For LLM-powered features, you can set up API keys: + +**Option 1: Environment Variables** +```bash +export OPENAI_API_KEY="your-openai-api-key" +export ANTHROPIC_API_KEY="your-anthropic-api-key" +export GOOGLE_API_KEY="your-google-api-key" +``` + +**Option 2: .env File** +Create a `.env` file in the project root: +``` +OPENAI_API_KEY=your-openai-api-key-here +ANTHROPIC_API_KEY=your-anthropic-api-key-here +GOOGLE_API_KEY=your-google-api-key-here +``` + +**Note:** Many demos work without any API keys using fallback classification! -# Splitter Demo -python examples/splitter_demo.py +### Key Features Demonstrated + +- **Intent Classification**: LLM-powered intent routing +- **Argument Extraction**: Automatic parameter extraction from user input +- **Context Management**: Shared state across handlers +- **Error Handling**: Robust error handling and debugging +- **Multi-Intent**: Handling complex, multi-part requests +- **Local Models**: Using Ollama for local LLM processing + +### Example Inputs + +**Simple Demo Inputs:** +- "Hello, my name is Alice" +- "What's 15 plus 7?" +- "Weather in San Francisco" +- "Help me" +- "Multiply 8 and 3" + +**Multi-Intent Demo Inputs:** +- "Hello Alice and what's the weather in San Francisco" +- "Calculate 5 plus 3 and also greet Bob" +- "Help me and get weather for New York" + +### Minimal Example + +Here's the absolute minimum code needed to get started: + +```python +from intent_kit import IntentGraphBuilder, handler, llm_classifier + +def create_intent_graph(): + handlers = [ + handler( + name="greet", + description="Greet the user", + handler_func=lambda name, **kwargs: f"Hello {name}!", + param_schema={"name": str} + ), + handler( + name="calculate", + description="Perform a calculation", + handler_func=lambda operation, a, b, **kwargs: f"{a} {operation} {b} = {eval(f'{a} {operation} {b}')}", + param_schema={"operation": str, "a": float, "b": float} + ) + ] + + classifier = llm_classifier( + name="root", + children=handlers, + llm_config={}, # Empty config uses fallback classification + description="Main intent classifier" + ) + + return IntentGraphBuilder().root(classifier).build() + +# Use the graph +graph = create_intent_graph() +result = graph.route("Hello, my name is Alice") +print(result.output) # "Hello Alice!" ``` --- diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 75ae14a..0000000 --- a/examples/README.md +++ /dev/null @@ -1,244 +0,0 @@ -# IntentKit Examples - -This directory contains simplified examples demonstrating IntentKit functionality. - -## Available Examples - -### Simple Demo (`simple_demo.py`) -A basic demonstration of IntentKit with LLM-powered intent classification and argument extraction. Shows the core IntentGraph functionality with a **pass-through splitter** (default behavior). - -### Multi-Intent Demo (`multi_intent_demo.py`) -**NEW!** A demonstration of multi-intent handling using the rule-based splitter. Shows how to handle complex inputs like "Hello Alice and what's the weather in San Francisco". - -### Error Demo (`error_demo.py`) -A demonstration of error handling and debugging features. Shows how to handle various error scenarios and debug intent routing issues. - -### Context Demo (`context_demo.py`) -A demonstration of context and dependency management. Shows how handlers can read from and write to shared context. - -### Context Debugging Demo (`context_debug_demo.py`) -**NEW!** A comprehensive demonstration of the new context debugging features including: -- `debug_context` and `context_trace` parameters -- Dependency mapping and analysis with `get_context_dependencies()` -- Context flow validation with `validate_context_flow()` -- Debug output formats (console, JSON) with `trace_context_execution()` - -### Ollama Demo (`ollama_demo.py`) -A demonstration of using IntentKit with local Ollama models. Shows how to configure and use local LLM models. - -## Default Behavior - -By default, IntentKit uses a **pass-through splitter** that doesn't split user input. This is the safest approach for most use cases, as it avoids accidentally splitting inputs like "What's 15 plus 7?" on mathematical operators. - -If you need multi-intent handling, explicitly configure the rule-based splitter: - -```python -from intent_kit.splitters import rule_splitter - -return IntentGraphBuilder().root(classifier).splitter(rule_splitter).build() -``` - -## Running the Examples - -1. Set up your environment variables (see individual demos for requirements) -2. Run any demo: `python examples/simple_demo.py` -3. For Ollama demo, ensure Ollama is running and you have a model pulled - -## Key Features Demonstrated - -- **Intent Classification**: LLM-powered intent routing -- **Argument Extraction**: Automatic parameter extraction from user input -- **Context Management**: Shared state across handlers -- **Error Handling**: Robust error handling and debugging -- **Multi-Intent**: Handling complex, multi-part requests -- **Local Models**: Using Ollama for local LLM processing - -## Quick Start - -### Minimal Example -```python -from intent_kit import IntentGraphBuilder, handler, llm_classifier - -def create_intent_graph(): - handlers = [ - handler( - name="greet", - description="Greet the user", - handler_func=lambda name: f"Hello {name}!", - param_schema={"name": str} - ), - handler( - name="calculate", - description="Perform a calculation", - handler_func=lambda operation, a, b: f"{a} {operation} {b} = {eval(f'{a} {operation} {b}')}", - param_schema={"operation": str, "a": float, "b": float} - ) - ] - - classifier = llm_classifier( - name="root", - children=handlers, - llm_config={}, # Empty config uses fallback classification - description="Main intent classifier" - ) - - return IntentGraphBuilder().root(classifier).build() - -# Use the graph -graph = create_intent_graph() -result = graph.route("Hello, my name is Alice") -print(result.output) # "Hello Alice!" -``` - -## Setup Requirements - -### API Keys for LLM Services (Optional) - -For LLM-powered features, you can set up API keys: - -#### Option 1: Environment Variables -```bash -export OPENAI_API_KEY="your-openai-api-key" -export ANTHROPIC_API_KEY="your-anthropic-api-key" -export GOOGLE_API_KEY="your-google-api-key" -``` - -#### Option 2: .env File -Create a `.env` file in the project root: -``` -OPENAI_API_KEY=your-openai-api-key-here -ANTHROPIC_API_KEY=your-anthropic-api-key-here -GOOGLE_API_KEY=your-google-api-key-here -``` - -**Note:** The minimal and rule-based demos work without any API keys! - -## Running the Examples - -```bash -# Start with the minimal demo (no API key required) -python examples/minimal_demo.py - -# Simple demo with LLM features -python examples/simple_demo.py - -# Splitter demo for multi-intent handling -python examples/splitter_demo.py - -# New API demo showing different configurations -python examples/new_api_demo.py - -# Rule-based demo (no API key required) -python examples/rule_based_demo.py -``` - -## What Each Demo Shows - -### Minimal Demo (`minimal_demo.py`) -- **Simplest configuration** - Absolute minimum code needed -- **No API dependencies** - Works without any LLM API keys -- **Basic intent handling** - Greeting and calculation intents -- **Fallback classification** - Uses simple keyword matching - -### Simple Demo (`simple_demo.py`) -- **LLM-powered classification** - Using LLMs to classify user intents -- **LLM-powered argument extraction** - Extracting structured parameters -- **Multiple intent types** - Greeting, calculations, weather, and help -- **Error handling** - How the system handles various inputs - -### Splitter Demo (`splitter_demo.py`) -- **Multi-intent handling** - Processing complex requests -- **Rule-based splitting** - Keyword-based logic for splitting -- **LLM-powered splitting** - AI-powered intelligent splitting -- **Child result tracking** - Managing multiple executions - -### New API Demo (`new_api_demo.py`) -- **Mixed configurations** - LLM-based and rule-based extraction -- **Different handler types** - Various parameter extraction methods -- **Auto-wired descriptions** - Automatic classifier configuration - -### Rule-Based Demo (`rule_based_demo.py`) -- **No LLM dependencies** - Works entirely offline -- **Fallback behavior** - Simple keyword-based classification -- **Rule-based extraction** - Basic parameter extraction -- **Offline development** - Perfect for testing without API costs - -## Example Inputs - -### Minimal Demo Inputs -- "Hello, my name is Alice" -- "What's 15 plus 7?" -- "Help me" - -### Simple Demo Inputs -- "Hello, my name is Alice" -- "What's 15 plus 7?" -- "Weather in San Francisco" -- "Help me" -- "Multiply 8 and 3" - -### Splitter Demo Inputs -- "Hello Alice and what's the weather in San Francisco" -- "Calculate 5 plus 3 and also greet Bob" -- "Help me and get weather for New York" - -## Key Concepts - -### Handler Configuration -```python -# LLM-based argument extraction -handler( - name="greet", - description="Greet the user", - handler_func=lambda name: f"Hello {name}!", - param_schema={"name": str}, - llm_config=LLM_CONFIG # Enables LLM extraction -) - -# Rule-based argument extraction -handler( - name="greet", - description="Greet the user", - handler_func=lambda name: f"Hello {name}!", - param_schema={"name": str} - # No llm_config = rule-based extraction -) -``` - -### Classifier Configuration -```python -# LLM-powered classification -llm_classifier( - name="root", - children=handlers, - llm_config=LLM_CONFIG, - description="Main classifier" -) - -# Fallback classification (no API key needed) -llm_classifier( - name="root", - children=handlers, - llm_config={}, # Empty config uses fallback - description="Main classifier" -) -``` - -### Graph Building -```python -# Simple graph -graph = IntentGraphBuilder().root(classifier).build() - -# With splitter for multi-intent -graph = IntentGraphBuilder().root(splitter).build() -``` - -## Next Steps - -1. **Start with `minimal_demo.py`** - Understand the basic structure -2. **Try `rule_based_demo.py`** - See how it works without LLMs -3. **Explore `simple_demo.py`** - Add LLM-powered features -4. **Check `splitter_demo.py`** - Handle complex multi-intent inputs -5. **Review `new_api_demo.py`** - Mix different configuration approaches - -All demos are designed to be minimal and focused on the intent graph configuration, with minimal boilerplate code. \ No newline at end of file diff --git a/examples/simple_demo.ipynb b/examples/simple_demo.ipynb index fad8edb..7800d7d 100644 --- a/examples/simple_demo.ipynb +++ b/examples/simple_demo.ipynb @@ -7,7 +7,268 @@ "metadata": {}, "outputs": [], "source": [ - "print(\"Hello, World!\")" + "OPENAI_API_KEY = \"YOUR_OPENAI_API_KEY_HERE\"" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2cc5b06c", + "metadata": {}, + "outputs": [], + "source": [ + "from intent_kit import IntentGraphBuilder, handler, llm_classifier\n", + "\n", + "# LLM configuration (remove if not using LLM-powered features)\n", + "LLM_CONFIG = {\n", + " \"provider\": \"openai\",\n", + " \"api_key\": OPENAI_API_KEY,\n", + " \"model\": \"gpt-4.1-mini\",\n", + "}\n", + "\n", + "\n", + "def _calculate_handler(operation: str, a: float, b: float) -> str:\n", + " \"\"\"Handle calculation with proper operator mapping.\"\"\"\n", + " operation_map = {\n", + " \"plus\": \"+\",\n", + " \"add\": \"+\",\n", + " \"minus\": \"-\",\n", + " \"subtract\": \"-\",\n", + " \"times\": \"*\",\n", + " \"multiply\": \"*\",\n", + " \"multiplied\": \"*\",\n", + " \"divided\": \"/\",\n", + " \"divide\": \"/\",\n", + " \"over\": \"/\",\n", + " }\n", + " math_op = operation_map.get(operation.lower(), operation)\n", + " try:\n", + " result = eval(f\"{a} {math_op} {b}\")\n", + " return f\"{a} {operation} {b} = {result}\"\n", + " except (SyntaxError, ZeroDivisionError) as e:\n", + " return f\"Error: Cannot calculate {a} {operation} {b} - {str(e)}\"\n", + "\n", + "\n", + "def create_intent_graph():\n", + " \"\"\"Create and configure the intent graph.\"\"\"\n", + " handlers = [\n", + " handler(\n", + " name=\"greet\",\n", + " description=\"Greet the user\",\n", + " handler_func=lambda name, **kwargs: f\"Hello {name}!\",\n", + " param_schema={\"name\": str},\n", + " llm_config=LLM_CONFIG,\n", + " ),\n", + " handler(\n", + " name=\"calculate\",\n", + " description=\"Perform a calculation\",\n", + " handler_func=lambda operation, a, b, **kwargs: _calculate_handler(\n", + " operation, a, b\n", + " ),\n", + " param_schema={\"operation\": str, \"a\": float, \"b\": float},\n", + " llm_config=LLM_CONFIG,\n", + " ),\n", + " handler(\n", + " name=\"weather\",\n", + " description=\"Get weather information\",\n", + " handler_func=lambda location, **kwargs: f\"Weather in {location}: 72°F, Sunny (simulated)\",\n", + " param_schema={\"location\": str},\n", + " llm_config=LLM_CONFIG,\n", + " ),\n", + " handler(\n", + " name=\"help\",\n", + " description=\"Get help\",\n", + " handler_func=lambda **kwargs: \"I can help with greetings, calculations, and weather!\",\n", + " param_schema={},\n", + " llm_config=LLM_CONFIG,\n", + " ),\n", + " ]\n", + " classifier = llm_classifier(\n", + " name=\"root\",\n", + " children=handlers,\n", + " llm_config=LLM_CONFIG,\n", + " description=\"Main intent classifier\",\n", + " )\n", + " return IntentGraphBuilder().root(classifier).build()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ab17590e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[INFO]\u001b[0m [intent_kit.graph.intent_graph] Added root node: root\n", + "\u001b[32m[INFO]\u001b[0m [intent_kit.graph.intent_graph] Validating graph structure...\n", + "\u001b[34m[DEBUG]\u001b[0m [intent_kit.graph.validation] Validating node types...\n", + "\u001b[32m[INFO]\u001b[0m [intent_kit.graph.validation] Node type validation passed ✓\n", + "\u001b[34m[DEBUG]\u001b[0m [intent_kit.graph.validation] Validating splitter-to-classifier routing constraints...\n", + "\u001b[32m[INFO]\u001b[0m [intent_kit.graph.validation] Splitter routing validation passed ✓\n", + "\u001b[34m[DEBUG]\u001b[0m [intent_kit.graph.validation] Validating graph structure...\n", + "\u001b[34m[DEBUG]\u001b[0m [intent_kit.graph.validation] Validating splitter-to-classifier routing constraints...\n", + "\u001b[32m[INFO]\u001b[0m [intent_kit.graph.validation] Splitter routing validation passed ✓\n", + "\u001b[32m[INFO]\u001b[0m [intent_kit.graph.validation] Graph validation complete: 5 total nodes, routing valid: True, cycles: False\n", + "\u001b[32m[INFO]\u001b[0m [intent_kit.graph.intent_graph] Graph validation completed successfully\n", + "\u001b[32m[INFO]\u001b[0m [intent_kit.graph.intent_graph] Graph validation passed after adding root node\n", + "\n", + "Input: Hello, my name is Alice\n", + "\u001b[34m[DEBUG]\u001b[0m [root] Classifier at 'root' routed input to 'greet'.\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor config: {'provider': 'openai', 'api_key': '***OBFUSCATED***', 'model': 'gpt-4.1-mini'}\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor prompt: You are a parameter extractor. Given a user input, extract the required parameters.\n", + "\n", + "User Input: Hello, my name is Alice\n", + "\n", + "Required Parameters:\n", + "- name: str\n", + "\n", + "\n", + "\n", + "Instructions:\n", + "- Extract the required parameters from the user input\n", + "- Consider the available context information to help with extraction\n", + "- Return each parameter on a new line in the format: \"param_name: value\"\n", + "- If a parameter is not found, use a reasonable default or empty string\n", + "- Be specific and accurate in your extraction\n", + "\n", + "Extracted Parameters:\n", + "\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor extracted: {'name': 'Alice'}\n", + "Intent: intent_graph\n", + "Output: Hello Alice!\n", + "\n", + "Input: What's 15 plus 7?\n", + "\u001b[34m[DEBUG]\u001b[0m [root] Classifier at 'root' routed input to 'calculate'.\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor config: {'provider': 'openai', 'api_key': '***OBFUSCATED***', 'model': 'gpt-4.1-mini'}\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor prompt: You are a parameter extractor. Given a user input, extract the required parameters.\n", + "\n", + "User Input: What's 15 plus 7?\n", + "\n", + "Required Parameters:\n", + "- operation: str\n", + "- a: float\n", + "- b: float\n", + "\n", + "\n", + "\n", + "Instructions:\n", + "- Extract the required parameters from the user input\n", + "- Consider the available context information to help with extraction\n", + "- Return each parameter on a new line in the format: \"param_name: value\"\n", + "- If a parameter is not found, use a reasonable default or empty string\n", + "- Be specific and accurate in your extraction\n", + "\n", + "Extracted Parameters:\n", + "\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor extracted: {'operation': 'plus', 'a': '15', 'b': '7'}\n", + "Intent: intent_graph\n", + "Output: 15.0 plus 7.0 = 22.0\n", + "\n", + "Input: Weather in San Francisco\n", + "\u001b[34m[DEBUG]\u001b[0m [root] Classifier at 'root' routed input to 'weather'.\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor config: {'provider': 'openai', 'api_key': '***OBFUSCATED***', 'model': 'gpt-4.1-mini'}\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor prompt: You are a parameter extractor. Given a user input, extract the required parameters.\n", + "\n", + "User Input: Weather in San Francisco\n", + "\n", + "Required Parameters:\n", + "- location: str\n", + "\n", + "\n", + "\n", + "Instructions:\n", + "- Extract the required parameters from the user input\n", + "- Consider the available context information to help with extraction\n", + "- Return each parameter on a new line in the format: \"param_name: value\"\n", + "- If a parameter is not found, use a reasonable default or empty string\n", + "- Be specific and accurate in your extraction\n", + "\n", + "Extracted Parameters:\n", + "\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor extracted: {'location': 'San Francisco'}\n", + "Intent: intent_graph\n", + "Output: Weather in San Francisco: 72°F, Sunny (simulated)\n", + "\n", + "Input: Help me\n", + "\u001b[34m[DEBUG]\u001b[0m [root] Classifier at 'root' routed input to 'help'.\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor config: {'provider': 'openai', 'api_key': '***OBFUSCATED***', 'model': 'gpt-4.1-mini'}\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor prompt: You are a parameter extractor. Given a user input, extract the required parameters.\n", + "\n", + "User Input: Help me\n", + "\n", + "Required Parameters:\n", + "\n", + "\n", + "\n", + "\n", + "Instructions:\n", + "- Extract the required parameters from the user input\n", + "- Consider the available context information to help with extraction\n", + "- Return each parameter on a new line in the format: \"param_name: value\"\n", + "- If a parameter is not found, use a reasonable default or empty string\n", + "- Be specific and accurate in your extraction\n", + "\n", + "Extracted Parameters:\n", + "\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor extracted: {}\n", + "Intent: intent_graph\n", + "Output: I can help with greetings, calculations, and weather!\n", + "\n", + "Input: Multiply 8 and 3\n", + "\u001b[34m[DEBUG]\u001b[0m [root] Classifier at 'root' routed input to 'calculate'.\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor config: {'provider': 'openai', 'api_key': '***OBFUSCATED***', 'model': 'gpt-4.1-mini'}\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor prompt: You are a parameter extractor. Given a user input, extract the required parameters.\n", + "\n", + "User Input: Multiply 8 and 3\n", + "\n", + "Required Parameters:\n", + "- operation: str\n", + "- a: float\n", + "- b: float\n", + "\n", + "\n", + "\n", + "Instructions:\n", + "- Extract the required parameters from the user input\n", + "- Consider the available context information to help with extraction\n", + "- Return each parameter on a new line in the format: \"param_name: value\"\n", + "- If a parameter is not found, use a reasonable default or empty string\n", + "- Be specific and accurate in your extraction\n", + "\n", + "Extracted Parameters:\n", + "\n", + "\u001b[34m[DEBUG]\u001b[0m [llm_classifier] LLM arg extractor extracted: {'operation': 'multiply', 'a': '8', 'b': '3'}\n", + "Intent: intent_graph\n", + "Output: 8.0 multiply 3.0 = 24.0\n" + ] + } + ], + "source": [ + "# Test the graph\n", + "from intent_kit.context import IntentContext\n", + "\n", + "graph = create_intent_graph()\n", + "context = IntentContext(session_id=\"simple_demo\")\n", + "\n", + "test_inputs = [\n", + " \"Hello, my name is Alice\",\n", + " \"What's 15 plus 7?\",\n", + " \"Weather in San Francisco\",\n", + " \"Help me\",\n", + " \"Multiply 8 and 3\",\n", + "]\n", + "\n", + "for user_input in test_inputs:\n", + " print(f\"\\nInput: {user_input}\")\n", + " result = graph.route(user_input, context=context)\n", + " if result.success:\n", + " print(f\"Intent: {result.node_name}\")\n", + " print(f\"Output: {result.output}\")\n", + " else:\n", + " print(f\"Error: {result.error}\")" ] } ], @@ -18,7 +279,15 @@ "name": "python3" }, "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", "version": "3.11.11" } }, diff --git a/examples/simple_demo.py b/examples/simple_demo.py index 8b950d6..8765e44 100644 --- a/examples/simple_demo.py +++ b/examples/simple_demo.py @@ -5,8 +5,8 @@ """ import os -from intent_kit import IntentGraphBuilder, handler, llm_classifier from dotenv import load_dotenv +from intent_kit import IntentGraphBuilder, handler, llm_classifier load_dotenv() diff --git a/pyproject.toml b/pyproject.toml index eb8506c..a3fa0f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,9 +19,6 @@ classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", ] -dependencies = [ - # No core dependencies - truly universal framework -] [project.optional-dependencies] openai = [ @@ -66,6 +63,7 @@ dev = [ "ipykernel>=6.0.0", "types-pyyaml>=6.0.12.20250516", "types-networkx>=3.5.0.20250701", + "python-dotenv>=1.1.1", ] viz = [ "networkx>=3.5", diff --git a/uv.lock b/uv.lock index 722b855..ba278a0 100644 --- a/uv.lock +++ b/uv.lock @@ -611,8 +611,8 @@ wheels = [ ] [[package]] -name = "intent-kit" -version = "0.1.0" +name = "intentkit-py" +version = "0.1.2" source = { editable = "." } [package.optional-dependencies] @@ -683,6 +683,7 @@ dev = [ { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest-cov", specifier = ">=5.0" }, { name = "python-dotenv", specifier = ">=1.0.0" }, + { name = "python-dotenv", specifier = ">=1.1.1" }, { name = "pyyaml" }, { name = "ruff", specifier = ">=0.4.7" }, { name = "tqdm" }, @@ -1679,11 +1680,11 @@ wheels = [ [[package]] name = "python-dotenv" -version = "1.1.0" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] [[package]]