Client API (Advanced)
Note: For most users, we recommend using the Notebook API with the
luiinterface. This page documents the lower-level client API for advanced use cases.
Overview
The LouieClient is the core class that handles communication with the Louie.ai service. While it's now an internal class (louieai._client.LouieClient), you can still access its functionality through the public API.
Creating a Client
Using the louie() Factory (Recommended)
from louieai import louie
import graphistry
# First authenticate with Graphistry
graphistry.register(api=3, username="your_user", password="your_pass")
# Create a callable cursor
lui = louie()
# Use it like the notebook API
response = lui("Analyze the network patterns in my dataset")
print(lui.text)
Direct Instantiation (Advanced)
For advanced use cases where you need direct access to the client:
from louieai import louie
import graphistry
# Authenticate and get graphistry client
g = graphistry.register(api=3, username="your_user", password="your_pass")
# Create cursor with specific configuration
lui = louie(g)
# Access the underlying client if needed
client = lui._client # Note: This is accessing internal API
Thread Management
Using the Cursor API
from louieai import louie
# Create cursor
lui = louie()
# Threads are managed automatically
lui("Start analyzing the network data")
thread_id = lui._client._current_thread_id # Access current thread
# Continue in same thread
lui("Focus on the largest connected component")
# Start a new thread
lui("", display=False) # Empty query creates new thread
lui("New topic here")
Low-Level Thread Operations
If you need direct thread control:
from louieai import louie
lui = louie()
# Access the internal client
client = lui._client
# List threads (optionally filter by folder)
threads = client.list_threads(folder="Investigations/Q4")
for thread in threads:
print(f"{thread.id}: {thread.name}")
# Get specific thread
thread = client.get_thread(thread_id)
# Get by name (server resolves name -> thread)
thread = client.get_thread_by_name("Sales Analysis")
Response Handling
Responses are automatically parsed and made available through the cursor interface:
from louieai import louie
lui = louie()
# Make a query
lui("Generate a summary report of the data")
# Access different response types
if lui.text:
print("Text response:", lui.text)
if lui.df is not None:
print("DataFrame shape:", lui.df.shape)
# Access raw response elements
for element in lui.elements:
print(f"Element type: {element['type']}")
Final answers, reasoning, and execution progress
Reasoning is opt-in and remains separate from the final answer. The default is final-only, so existing code keeps the same response shape.
response = client.add_cell(
"",
"Investigate the anomaly and explain the evidence",
include_reasoning=True,
traces=True,
)
# Final-oriented compatibility surface
print(response.text)
print(response.final_text)
# Provisional reasoning is explicit and may contain multiple durable parts
print(response.reasoning_text)
for part in response.reasoning_elements:
print(part["text"], part.get("complete"))
# Execution progress and observability
print(response.status) # succeeded, failed, cancelled, interrupted, ...
print(response.phases) # latest MethodRun snapshots
print(response.token_flow) # optional token counters
print(response.trace_events) # returned server trace payloads
print(response.terminal_error) # final stream plumbing error, if any
The current server does not put a draft flag on text elements. It identifies
the final answer through the root run's final_answer element ID; the client
uses that pointer to keep .text final-oriented. complete describes whether
an element is still streaming, not whether it is reasoning.
For protocol-level debugging, response.stream_messages preserves every typed
stream envelope in order, including unknown future message types. Uploads may
emit multiple terminal envelopes, so response.terminal is the last one and
response.terminals retains the full history.
Error Handling
from louieai import louie
# Example showing error handling (requires authentication)
# lui = louie()
#
# try:
# lui("Query the sales database")
# except RuntimeError as e:
# if "No Graphistry API token" in str(e):
# print("Please authenticate with graphistry.register() first")
# elif "API returned error" in str(e):
# print(f"Server error: {e}")
# elif "Failed to connect" in str(e):
# print(f"Network error: {e}")
# Working example with mock authentication
import os
os.environ['GRAPHISTRY_USERNAME'] = 'test'
os.environ['GRAPHISTRY_PASSWORD'] = 'test'
# In practice, use real credentials or graphistry.register()
Configuration Options
Custom Server URL
from louieai import louie
# Use a custom Louie server
lui = louie(server_url="https://custom.louie.ai")
# Use a custom Graphistry auth server alongside Louie
lui = louie(
server_url="https://louie.your-company.com",
graphistry_server="your-company.graphistry.com",
)
Authentication Methods
from louieai import louie
# Username/Password
lui = louie(username="user", password="pass")
# Personal Key (Service Account)
lui = louie(
personal_key_id="pk_123",
personal_key_secret="sk_456",
org_name="my-org"
)
# API Key (Legacy)
lui = louie(api_key="your_api_key")
# Direct bearer token (Graphistry or anonymous)
lui = louie(token="<token>")
# Anonymous auth (desktop/local, if enabled)
# Use the Tornado/UI port so /auth/anonymous is available
lui = louie(server_url="http://localhost:8513", anonymous=True)
Anonymous auth is optional on the server and cannot be combined with Graphistry credentials.
Reasoning and trace controls
from louieai import louie
lui = louie()
# Provisional model reasoning is a separate, default-off stream
lui.include_reasoning = True
lui("Complex analysis query")
print(lui.reasoning_text)
# Server trace events are an independent debugging channel
lui.traces = True
lui("Another query")
print(lui.trace_events)
include_reasoning controls provisional text. traces requests server log
trace events, returned as trace_events. W3C traceparent propagation is a
third concern used to correlate the HTTP request with distributed telemetry.
Timeout Configuration
Agentic flows can take significant time to complete. The client provides configurable timeouts:
from louieai import louie
# Default timeouts (5 minutes total, 2 minutes per streaming chunk)
lui = louie()
# Custom timeouts for long-running analysis
lui = louie(
timeout=600, # 10 minutes total
streaming_timeout=180 # 3 minutes per chunk
)
# Using environment variables
# export LOUIE_TIMEOUT=600
# export LOUIE_STREAMING_TIMEOUT=180
lui = louie() # Will use env var settings
Table AI Overrides
When you need to steer Table AI’s semantic map/reduce behaviour or pick specific models, pass a TableAIOverrides dataclass through the louie() cursor (it propagates to LouieClient.add_cell()):
from louieai import louie, TableAIOverrides
lui = louie()
overrides = TableAIOverrides(
semantic_mode="map",
output_column="semantic_map",
ask_model="gpt-4o-mini",
evidence_model="gpt-4o",
options={"max_rows": 5},
)
response = lui(
"Summarize customer cohorts",
table_ai_overrides=overrides,
)
for element in response.elements:
print(element["type"], element.get("text"))
Uploads support the same overrides:
df_response = lui._client.upload_dataframe(
prompt="Cluster transactions by geography",
df=df,
table_ai_overrides=TableAIOverrides(
semantic_mode="reduce",
output_column="semantic_reduce",
ask_options={"temperature": 0.2},
),
)
If you see timeout errors, the client will provide helpful guidance about increasing timeouts.
Migration from Direct LouieClient
If you have code using the old LouieClient directly:
# Old way (no longer public)
# from louieai import LouieClient
# client = LouieClient()
# response = client.add_cell("", "Query")
# New way
from louieai import louie
lui = louie()
lui("Query")
response = lui.response # If you need the raw response
See Also
- Notebook API Reference - Recommended high-level API
- Response Types - Understanding response formats
- Authentication Guide - Detailed auth setup