Releases: igorbenav/clientai
0.5.0
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:
- Regular text output (default)
- JSON-formatted output (
json_output=True
) - Validated output with Pydantic models (
json_output=True
withreturn_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
What's Changed
- improved example by @igorbenav in #31
- rename 'full' extra to 'all' in pyproject.toml by @jpwieland in #32
- version bumped to 0.4.4 by @igorbenav in #33
New Contributors
- @jpwieland made their first contribution in #32 🎉
Full Changelog: v0.4.3...v0.4.4
0.4.3
What's Changed
- fix number of passed parameters bug by @igorbenav in #29
- bump for version by @igorbenav in #30
Full Changelog: v0.4.2...v0.4.3
0.4.2
What's Changed
- version update by @igorbenav in #24
- Docs improvement by @igorbenav in #25
- correct url for template by @igorbenav in #26
- fix for httpx proxies deprecation by @igorbenav in #27
- package version bump by @igorbenav in #28
Full Changelog: v0.4.1...v0.4.2
0.4.1
Small bug fix
What's Changed
- add previously removed _current_agent by @igorbenav in #14
Full Changelog: v0.4.0...v0.4.1
0.4.0
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 stepsact
: Decision-making and action stepsobserve
: Data gathering stepssynthesize
: Summary and integration stepsall
: 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.
-
Step Organization: Organize workflow steps logically, with clear progression from analysis to action.
-
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
-
Error Handling:
- Configure step requirements appropriately
- Use retry counts for potentially flaky operations
- Implement proper error recovery in custom run methods
-
Performance:
- Use streaming for long-running operations
- Configure appropriate model parameters for each step type
- Manage context size to prevent memory issues
-
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:
- Mock the ClientAI instance for testing
- Test tool selection logic independently
- Verify step execution order
- Test error handling paths
- Validate context management
- 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
##...
0.3.3
0.3.2
0.3.1
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
- Both
model
andprompt
/messages
are required - Model must be one of: "gemma-7b-it", "llama3-70b-8192", "llama3-8b-8192", "mixtral-8x7b-32768"
n
parameter only supports value of 1stop
sequences limited to 4 maximum- Tool usage limited to 128 functions
response_format
requires explicit JSON instruction in prompt- Parameters like
logprobs
,logit_bias
, andtop_logprobs
are not yet supported - Deterministic generation with
seed
is best-effort functions
andfunction_call
are deprecated in favor oftools
andtool_choice
These parameters allow you to fully customize Groq's behavior while working with ClientAI's abstraction layer.
What's Changed
- documentation fixes by @igorbenav in #7
- Groq support by @igorbenav in #8
- mkdocs settings fix by @igorbenav in #9
Full Changelog: v0.3.0...v0.3.1
0.3.0
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
-
Context Manager (Recommended)
with OllamaManager() as manager: # Server starts automatically client = ClientAI('ollama') # Use client... # Server stops automatically
-
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
- Ollama manager added by @igorbenav in #5
Full Changelog: v0.2.1...v0.3.0