Skip to content

Releases: igorbenav/clientai

0.5.0

29 Dec 06:10
040ecb9
Compare
Choose a tag to compare

Pydantic Validation for each Step

To see a more detailed guide, check the docs here.

You now have three ways to handle agent outputs in ClientAI:

  1. Regular text output (default)
  2. JSON-formatted output (json_output=True)
  3. Validated output with Pydantic models (json_output=True with return_type)

Let's look at when to use each approach.

Simple Text Output

When you just need text responses, use the default configuration:

class SimpleAgent(Agent):
    @think("analyze")
    def analyze_text(self, input_text: str) -> str:
        return f"Please analyze this text: {input_text}"

# Usage
agent = SimpleAgent(client=client, default_model="gpt-4")
result = agent.run("Hello world")  # Returns plain text

This is perfect for general text generation, summaries, or when you don't need structured data.

JSON-Formatted Output

When you need structured data but don't want strict validation, use json_output=True:

class StructuredAgent(Agent):
    @think(
        name="analyze",
        json_output=True  # Ensures JSON output
    )
    def analyze_data(self, input_data: str) -> str:
        return """
        Analyze this data and return as JSON with these fields:
        - summary: brief overview
        - key_points: list of main points
        - sentiment: positive, negative, or neutral
        
        Data: {input_data}
        """

# Usage
agent = StructuredAgent(client=client, default_model="gpt-4")
result = agent.run("Great product, highly recommend!")
# Returns parsed JSON like:
# {
#     "summary": "Positive product review",
#     "key_points": ["Strong recommendation", "General satisfaction"],
#     "sentiment": "positive"
# }

This approach gives you structured data while maintaining flexibility in the output format.

Validated Output with Pydantic

When you need guaranteed output structure and type safety, combine json_output with Pydantic models:

from pydantic import BaseModel, Field
from typing import List, Optional

class ProductAnalysis(BaseModel):
    summary: str = Field(
        min_length=10,
        description="Brief overview of the analysis"
    )
    key_points: List[str] = Field(
        min_items=1,
        description="Main points from the analysis"
    )
    sentiment: str = Field(
        pattern="^(positive|negative|neutral)$",
        description="Overall sentiment"
    )
    confidence: float = Field(
        ge=0, le=1,
        description="Confidence score between 0 and 1"
    )
    categories: Optional[List[str]] = Field(
        default=None,
        description="Product categories if mentioned"
    )

class ValidatedAgent(Agent):
    @think(
        name="analyze",
        json_output=True,  # Required for validation
        return_type=ProductAnalysis  # Enables Pydantic validation
    )
    def analyze_review(self, review: str) -> ProductAnalysis:
        return """
        Analyze this product review and return a JSON object with:
        - summary: at least 10 characters
        - key_points: non-empty list of strings
        - sentiment: exactly "positive", "negative", or "neutral"
        - confidence: number between 0 and 1
        - categories: optional list of product categories
        
        Review: {review}
        """

# Usage
agent = ValidatedAgent(client=client, default_model="gpt-4")
try:
    result = agent.run("This laptop is amazing! Great battery life and performance.")
    print(f"Summary: {result.summary}")
    print(f"Sentiment: {result.sentiment}")
    print(f"Confidence: {result.confidence}")
    for point in result.key_points:
        print(f"- {point}")
except ValidationError as e:
    print("Output validation failed:", e)

0.4.4

17 Dec 18:02
0f42f4e
Compare
Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v0.4.3...v0.4.4

0.4.3

16 Dec 03:14
c1d965e
Compare
Choose a tag to compare

What's Changed

Full Changelog: v0.4.2...v0.4.3

0.4.2

16 Dec 01:05
d52d1d2
Compare
Choose a tag to compare

What's Changed

Full Changelog: v0.4.1...v0.4.2

0.4.1

15 Dec 02:52
62f043b
Compare
Choose a tag to compare

Small bug fix

What's Changed

Full Changelog: v0.4.0...v0.4.1

0.4.0

14 Dec 23:59
5d5f91a
Compare
Choose a tag to compare

ClientAI Agent Module

The Agent module provides a flexible framework for building AI agents that can execute multi-step workflows with automated tool selection and LLM integration. This module enables developers to create sophisticated AI agents that can handle complex tasks through configurable steps, automated tool selection, and state management.

For complete API reference, see Agent API Documentation.

Core Features

  • Multi-step workflow execution with LLM integration
  • Automated tool selection and execution
  • Configurable execution steps (think, act, observe, synthesize)
  • State and context management across steps
  • Streaming response support
  • Comprehensive error handling and retry logic
  • Tool scope management and validation

For detailed examples, see Agent Examples.

Quick Start

For a complete guide on creating agents, see Creating Agents.

from clientai import ClientAI
from clientai.agent import create_agent, tool

# Create a simple translation agent
translator = create_agent(
    client=client,
    role="translator",
    system_prompt="You are a helpful translation assistant. Translate input to French.",
    model="gpt-4"
)

result = translator.run("Hello world!")  # Returns: "Bonjour le monde!"

# Create an agent with tools
class AnalysisAgent(Agent):
    @think("analyze")
    def analyze_data(self, input_data: str) -> str:
        return f"Please analyze this data: {input_data}"
    
    @tool(name="Calculator")
    def calculate(self, x: int, y: int) -> int:
        """Performs basic arithmetic."""
        return x + y

agent = AnalysisAgent(
    client=client,
    default_model="gpt-4",
    tool_confidence=0.8
)

Core Components

Agent Class

The main Agent class provides the foundation for creating AI agents. See Agent API Reference for complete details.

class Agent:
    def __init__(
        self,
        client: ClientAI,
        default_model: Union[str, Dict[str, Any], ModelConfig],
        tools: Optional[List[ToolConfig]] = None,
        tool_selection_config: Optional[ToolSelectionConfig] = None,
        tool_confidence: Optional[float] = None,
        tool_model: Optional[Union[str, Dict[str, Any], ModelConfig]] = None,
        max_tools_per_step: Optional[int] = None,
        max_history_size: Optional[int] = None,
        **default_model_kwargs: Any
    )

Step Decorators

The module provides decorators for defining workflow steps. For more information, see Workflow Steps and Step Decorators API.

from clientai.agent import think, act, observe, synthesize

class MyAgent(Agent):
    @think("analyze")
    def analyze_data(self, input_data: str) -> str:
        return f"Analyze this data: {input_data}"
    
    @act("process")
    def process_results(self, analysis: str) -> str:
        return f"Process these results: {analysis}"
    
    @observe("gather")
    def gather_data(self, query: str) -> str:
        return f"Gathering data for: {query}"
    
    @synthesize("summarize")
    def summarize_results(self, data: str) -> str:
        return f"Summary of: {data}"

Tool Management

Tools can be registered and managed using several approaches. For complete documentation, see Tools and Tool Selection.

# Using the @tool decorator
@tool(name="Calculator", description="Performs calculations")
def calculate(x: int, y: int) -> int:
    return x + y

# Direct registration
agent.register_tool(
    utility_function,
    name="Utility",
    description="Utility function",
    scopes=["think", "act"]
)

# Using ToolConfig
tool_config = ToolConfig(
    tool=calculate,
    scopes=["think", "act"],
    name="Calculator",
    description="Performs calculations"
)

Configuration

ModelConfig

Configure model behavior. See Agent Models for details.

from clientai.agent.config import ModelConfig

model_config = ModelConfig(
    name="gpt-4",
    temperature=0.7,
    stream=True,
    json_output=False
)

StepConfig

Configure step execution behavior. See Step Configuration for details.

from clientai.agent.config import StepConfig

step_config = StepConfig(
    enabled=True,
    retry_count=3,
    timeout=30.0,
    required=True,
    pass_result=True
)

ToolSelectionConfig

Configure tool selection behavior. See Tool Selection for details.

from clientai.agent.config import ToolSelectionConfig

tool_config = ToolSelectionConfig(
    confidence_threshold=0.8,
    max_tools_per_step=3
)

Advanced Usage

Streaming Responses

For detailed information about streaming, see Creating Custom Run.

# Enable streaming for specific runs
for chunk in agent.run("Process this data", stream=True):
    print(chunk, end="", flush=True)

# Configure streaming at step level
class StreamingAgent(Agent):
    @think("analyze", stream=True)
    def analyze_data(self, input: str) -> str:
        return f"Analyzing: {input}"

Context Management

For complete context management documentation, see Context Management and AgentContext API.

# Access and manipulate agent context
agent.context.set_input("New input")
agent.context.state["key"] = "value"
agent.context.set_step_result("analyze", "Analysis result")

# Reset context
agent.reset_context()  # Clears current state
agent.reset()         # Complete reset including workflow

Custom Run Methods

For advanced run method customization, see Creating Custom Run.

class CustomAgent(Agent):
    @run(description="Custom workflow execution")
    def custom_run(self, input_data: str) -> str:
        # Custom workflow implementation
        result = self.analyze_step(input_data)
        return self.process_step(result)

Tool Scopes

Tools can be restricted to specific workflow steps. See Tool Registry API for complete details.

  • think: Analysis and reasoning steps
  • act: Decision-making and action steps
  • observe: Data gathering steps
  • synthesize: Summary and integration steps
  • all: Available in all steps
# Register tool with specific scopes
agent.register_tool(
    calculate,
    name="Calculator",
    scopes=["think", "act"]
)

# Get tools for a specific scope
think_tools = agent.get_tools("think")

Error Handling

For comprehensive error handling information, see Error Handling.

from clientai.agent.exceptions import (
    AgentError,     # Base exception for agent-related errors
    StepError,      # Errors in step execution
    WorkflowError,  # Errors in workflow management
    ToolError       # Errors in tool execution
)

try:
    result = agent.run("Process this")
except StepError as e:
    print(f"Step execution failed: {e}")
except ToolError as e:
    print(f"Tool execution failed: {e}")
except WorkflowError as e:
    print(f"Workflow execution failed: {e}")

Best Practices

For more detailed best practices and guidelines, see Advanced Overview.

  1. Step Organization: Organize workflow steps logically, with clear progression from analysis to action.

  2. Tool Design:

    • Provide clear type hints for tool functions
    • Include descriptive docstrings
    • Keep tools focused on single responsibilities
    • Use appropriate scopes to restrict tool availability
  3. Error Handling:

    • Configure step requirements appropriately
    • Use retry counts for potentially flaky operations
    • Implement proper error recovery in custom run methods
  4. Performance:

    • Use streaming for long-running operations
    • Configure appropriate model parameters for each step type
    • Manage context size to prevent memory issues
  5. Context Management:

    • Clear context when appropriate
    • Use state dictionary for temporary data
    • Maintain appropriate history size

Testing

For detailed testing guidelines, see Contributing Guide.

When testing agents:

  1. Mock the ClientAI instance for testing
  2. Test tool selection logic independently
  3. Verify step execution order
  4. Test error handling paths
  5. Validate context management
  6. Check streaming behavior

Example test setup:

from unittest.mock import Mock

def test_agent():
    mock_client = Mock()
    mock_client.generate_text.return_value = "Test response"
    
    agent = MyAgent(
        client=mock_client,
        default_model="gpt-4"
    )
    
    result = agent.run("Test input")
    assert result == "Test response"
    assert mock_client.generate_text.called

##...

Read more

0.3.3

02 Dec 05:53
e7810cc
Compare
Choose a tag to compare

What's Changed

Full Changelog: v0.3.2...v0.3.3

0.3.2

09 Nov 12:19
bd399ad
Compare
Choose a tag to compare

What's Changed

Full Changelog: v0.3.1...v0.3.2

0.3.1

03 Nov 21:58
04d449f
Compare
Choose a tag to compare

Groq Support

from clientai import ClientAI

# Initialize the Groq client
client = ClientAI('groq', host="your-ollama-host")

# Now you can use the client for text generation or chat

Groq-Specific Parameters in ClientAI

This guide covers the Groq-specific parameters that can be passed to ClientAI's generate_text and chat methods. These parameters are passed as additional keyword arguments to customize Groq's behavior.

generate_text Method

Basic Structure

from clientai import ClientAI

client = ClientAI('groq', api_key="your-groq-api-key")
response = client.generate_text(
    prompt="Your prompt here",          # Required
    model="llama3-8b-8192",            # Required
    frequency_penalty=0.5,              # Groq-specific
    presence_penalty=0.2,               # Groq-specific
    max_tokens=100,                     # Groq-specific
    response_format={"type": "json"},   # Groq-specific
    seed=12345,                        # Groq-specific
    temperature=0.7,                    # Groq-specific
    top_p=0.9,                         # Groq-specific
    n=1,                               # Groq-specific
    stop=["END"],                      # Groq-specific
    stream=False,                      # Groq-specific
    stream_options=None,               # Groq-specific
    functions=None,                    # Groq-specific (Deprecated)
    function_call=None,                # Groq-specific (Deprecated)
    tools=None,                        # Groq-specific
    tool_choice=None,                  # Groq-specific
    parallel_tool_calls=True,          # Groq-specific
    user="user_123"                    # Groq-specific
)

Groq-Specific Parameters

frequency_penalty: Optional[float]

  • Range: -2.0 to 2.0
  • Default: 0
  • Penalizes tokens based on their frequency in the text
response = client.generate_text(
    prompt="Write a creative story",
    model="llama3-8b-8192",
    frequency_penalty=0.7  # Reduces repetition
)

presence_penalty: Optional[float]

  • Range: -2.0 to 2.0
  • Default: 0
  • Penalizes tokens based on their presence in prior text
response = client.generate_text(
    prompt="Write a varied story",
    model="llama3-8b-8192",
    presence_penalty=0.6  # Encourages topic diversity
)

max_tokens: Optional[int]

  • Maximum tokens for completion
  • Limited by model's context length
response = client.generate_text(
    prompt="Write a summary",
    model="llama3-8b-8192",
    max_tokens=100
)

response_format: Optional[Dict]

  • Controls output structure
  • Requires explicit JSON instruction in prompt
response = client.generate_text(
    prompt="List three colors in JSON",
    model="llama3-8b-8192",
    response_format={"type": "json_object"}
)

seed: Optional[int]

  • For deterministic generation
response = client.generate_text(
    prompt="Generate a random number",
    model="llama3-8b-8192",
    seed=12345
)

temperature: Optional[float]

  • Range: 0 to 2
  • Default: 1
  • Controls randomness in output
response = client.generate_text(
    prompt="Write creatively",
    model="llama3-8b-8192",
    temperature=0.7  # More creative output
)

top_p: Optional[float]

  • Range: 0 to 1
  • Default: 1
  • Alternative to temperature, called nucleus sampling
response = client.generate_text(
    prompt="Generate text",
    model="llama3-8b-8192",
    top_p=0.1  # Only consider top 10% probability tokens
)

n: Optional[int]

  • Default: 1
  • Number of completions to generate
  • Note: Currently only n=1 is supported
response = client.generate_text(
    prompt="Generate a story",
    model="llama3-8b-8192",
    n=1
)

stop: Optional[Union[str, List[str]]]

  • Up to 4 sequences where generation stops
response = client.generate_text(
    prompt="Write until you see END",
    model="llama3-8b-8192",
    stop=["END", "STOP"]  # Stops at either sequence
)

stream: Optional[bool]

  • Default: False
  • Enable token streaming
for chunk in client.generate_text(
    prompt="Tell a story",
    model="llama3-8b-8192",
    stream=True
):
    print(chunk, end="", flush=True)

stream_options: Optional[Dict]

  • Options for streaming responses
  • Only used when stream=True
response = client.generate_text(
    prompt="Long story",
    model="llama3-8b-8192",
    stream=True,
    stream_options={"chunk_size": 1024}
)

user: Optional[str]

  • Unique identifier for end-user tracking
response = client.generate_text(
    prompt="Hello",
    model="llama3-8b-8192",
    user="user_123"
)

chat Method

Basic Structure

response = client.chat(
    model="llama3-8b-8192",            # Required
    messages=[...],                    # Required
    tools=[...],                      # Groq-specific
    tool_choice="auto",               # Groq-specific
    parallel_tool_calls=True,         # Groq-specific
    response_format={"type": "json"}, # Groq-specific
    temperature=0.7,                  # Groq-specific
    frequency_penalty=0.5,            # Groq-specific
    presence_penalty=0.2,             # Groq-specific
    max_tokens=100,                   # Groq-specific
    seed=12345,                      # Groq-specific
    stop=["END"],                    # Groq-specific
    stream=False,                    # Groq-specific
    stream_options=None,             # Groq-specific
    top_p=0.9,                       # Groq-specific
    n=1,                             # Groq-specific
    user="user_123"                  # Groq-specific
)

Groq-Specific Parameters

tools: Optional[List[Dict]]

  • List of available tools (max 128)
response = client.chat(
    model="llama3-70b-8192",
    messages=[{"role": "user", "content": "What's the weather?"}],
    tools=[{
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get weather data",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {"type": "string"}
                }
            }
        }
    }]
)

tool_choice: Optional[Union[str, Dict]]

  • Controls tool selection behavior
  • Values: "none", "auto", "required"
response = client.chat(
    model="llama3-70b-8192",
    messages=[{"role": "user", "content": "Calculate something"}],
    tool_choice="auto"  # or "none" or "required"
)

parallel_tool_calls: Optional[bool]

  • Default: True
  • Enable parallel function calling
response = client.chat(
    model="llama3-70b-8192",
    messages=[{"role": "user", "content": "Multiple tasks"}],
    parallel_tool_calls=True
)

Complete Examples

Example 1: Structured Output with Tools

response = client.chat(
    model="llama3-70b-8192",
    messages=[
        {"role": "system", "content": "You are a data assistant"},
        {"role": "user", "content": "Get weather for Paris"}
    ],
    response_format={"type": "json_object"},
    tools=[{
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get weather data",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {"type": "string"}
                }
            }
        }
    }],
    tool_choice="auto",
    temperature=0.7,
    max_tokens=200,
    seed=42
)

Example 2: Advanced Text Generation

response = client.generate_text(
    prompt="Write a technical analysis",
    model="mixtral-8x7b-32768",
    max_tokens=500,
    frequency_penalty=0.7,
    presence_penalty=0.6,
    temperature=0.4,
    top_p=0.9,
    stop=["END", "CONCLUSION"],
    user="analyst_1",
    seed=42
)

Example 3: Streaming Generation

for chunk in client.chat(
    model="llama3-8b-8192",
    messages=[{"role": "user", "content": "Explain quantum physics"}],
    stream=True,
    temperature=0.7,
    max_tokens=1000,
    stream_options={"chunk_size": 1024}
):
    print(chunk, end="", flush=True)

Parameter Validation Notes

  1. Both model and prompt/messages are required
  2. Model must be one of: "gemma-7b-it", "llama3-70b-8192", "llama3-8b-8192", "mixtral-8x7b-32768"
  3. n parameter only supports value of 1
  4. stop sequences limited to 4 maximum
  5. Tool usage limited to 128 functions
  6. response_format requires explicit JSON instruction in prompt
  7. Parameters like logprobs, logit_bias, and top_logprobs are not yet supported
  8. Deterministic generation with seed is best-effort
  9. functions and function_call are deprecated in favor of tools and tool_choice

These parameters allow you to fully customize Groq's behavior while working with ClientAI's abstraction layer.

What's Changed

Full Changelog: v0.3.0...v0.3.1

0.3.0

01 Nov 03:08
3c7d463
Compare
Choose a tag to compare

Ollama Manager Guide

Introduction

Ollama Manager provides a streamlined way to prototype and develop applications using Ollama's AI models. Instead of manually managing the Ollama server process, installing it as a service, or running it in a separate terminal, Ollama Manager handles the entire lifecycle programmatically.

Key Benefits for Prototyping:

  • Start/stop Ollama server automatically within your Python code
  • Configure resources dynamically based on your needs
  • Handle multiple server instances for testing
  • Automatic cleanup of resources
  • Platform-independent operation

Quick Start

from clientai import ClientAI
from clientai.ollama import OllamaManager

# Basic usage - server starts automatically and stops when done
with OllamaManager() as manager:
    # Create a client that connects to the managed server
    client = ClientAI('ollama', host="http://localhost:11434")
    
    # Use the client normally
    response = client.generate_text(
        "Explain quantum computing",
        model="llama2"
    )
    print(response)
# Server automatically stops when exiting the context

Installation

# Install with Ollama support
pip install "clientai[ollama]"

# Install with all providers
pip install "clientai[all]"

Core Concepts

Server Lifecycle Management

  1. Context Manager (Recommended)

    with OllamaManager() as manager:
        # Server starts automatically
        client = ClientAI('ollama')
        # Use client...
    # Server stops automatically
  2. Manual Management

    manager = OllamaManager()
    try:
        manager.start()
        client = ClientAI('ollama')
        # Use client...
    finally:
        manager.stop()

Configuration Management

from clientai.ollama import OllamaServerConfig

# Create custom configuration
config = OllamaServerConfig(
    host="127.0.0.1",
    port=11434,
    gpu_layers=35,
    memory_limit="8GiB"
)

# Use configuration with manager
with OllamaManager(config) as manager:
    client = ClientAI('ollama')
    # Use client...

For more information, see the docs.

What's Changed

Full Changelog: v0.2.1...v0.3.0