Construct Labs CRM Agent Environment
Python SDK for the Construct Labs CRM Agent Environment - a reinforcement learning environment for training AI agents to interact with CRM systems.
License
This software requires a commercial license from Construct Labs GmbH. Contact hello@construct-labs.com for licensing inquiries.
Installation
uv add construct-labs-crm-env
Try the Example Chat Agent
Run the interactive chat agent without installing:
# OpenRouter API key (https://openrouter.ai/keys)
export OPENAI_API_KEY=<api_key>
# Construct Labs CRM API Key (provided by Construct Labs)
export CRM_AGENT_API_KEY=<api_key>
uvx --from construct-labs-crm-env[chat] chat-agent
Quick Start
from construct_labs_crm_env import CrmAgentEnv, CrmAgentAction, CRMActionType
# Connect to the CRM environment
with CrmAgentEnv(
base_url="https://env.crm.construct-labs.com",
api_key="your-api-key" # Issued by Construct Labs
) as env:
# Reset the environment
result = env.reset()
# List companies
result = env.step(CrmAgentAction(
action_type=CRMActionType.LIST_COMPANIES,
limit=10
))
print(result.observation.data)
Environment Variables
You can set your API key via environment variable:
export CRM_AGENT_API_KEY=your-api-key
# API key is read from environment
env = CrmAgentEnv(base_url="https://env.crm.construct-labs.com")
LLM Integration Example
The SDK is designed to work with LLM-based agents. Here's how to parse LLM tool calls:
from construct_labs_crm_env import CrmAgentEnv
with CrmAgentEnv(
base_url="https://env.crm.construct-labs.com",
api_key="your-api-key"
) as env:
result = env.reset()
# Simulate an LLM generating a tool call
llm_tool_call = {
"name": "list_companies",
"arguments": {"limit": 5}
}
# Parse the tool call into a CrmAgentAction
parsed = env.parse_tool_call(llm_tool_call)
if parsed.is_valid:
result = env.step(parsed.action)
print(result.observation.model_dump_json(indent=2))
else:
print(f"Invalid tool call: {parsed.error_message}")
Customization
Subclass CrmAgentEnv to customize agent behavior:
Custom System Prompt
class SalesAgent(CrmAgentEnv):
@property
def system_prompt(self) -> str:
return """You are a sales assistant AI.
Your goal is to help close deals by:
1. Finding relevant companies and contacts
2. Creating opportunities with accurate values
3. Adding follow-up tasks
Be concise. Focus on high-value opportunities."""
Restricted Tool Set
class ReadOnlyAgent(CrmAgentEnv):
"""Agent that can only read data, not modify."""
@property
def tools(self) -> list[dict]:
read_only = {'list_companies', 'get_company', 'list_people',
'get_person', 'list_opportunities', 'submit_answer'}
return [t for t in self._default_tools()
if t['function']['name'] in read_only]
Custom Observation Formatting
class VerboseAgent(CrmAgentEnv):
def format_observation(self, observation):
base = super().format_observation(observation)
return f"=== CRM Response ===\n{base}\n=== End ==="
Available Actions
Company Operations
LIST_COMPANIES- List all companiesGET_COMPANY- Get a specific company by IDCREATE_COMPANY- Create a new companyUPDATE_COMPANY- Update an existing companyDELETE_COMPANY- Delete a company
Contact Operations
LIST_PEOPLE- List all contactsGET_PERSON- Get a specific contact by IDCREATE_PERSON- Create a new contactUPDATE_PERSON- Update an existing contactDELETE_PERSON- Delete a contact
Opportunity Operations
LIST_OPPORTUNITIES- List all opportunitiesGET_OPPORTUNITY- Get a specific opportunity by IDCREATE_OPPORTUNITY- Create a new opportunityUPDATE_OPPORTUNITY- Update an existing opportunityDELETE_OPPORTUNITY- Delete an opportunity
Note Operations
LIST_NOTES- List all notesCREATE_NOTE- Create a note attached to a record
Task Operations
LIST_TASKS- List all tasksCREATE_TASK- Create a new taskUPDATE_TASK- Update an existing taskCOMPLETE_TASK- Mark a task as complete
Submit Answer
SUBMIT_ANSWER- Submit the final answer and end the session
Integration with Training Frameworks
Collecting Rollouts for RL Training
The SDK is designed for reinforcement learning. Here's how to collect rollouts with rewards:
from dataclasses import dataclass, field
from construct_labs_crm_env import CrmAgentEnv, CrmAgentObservation
@dataclass
class Rollout:
"""A single episode rollout for training."""
observations: list[CrmAgentObservation] = field(default_factory=list)
actions: list[dict] = field(default_factory=list) # Raw tool calls
rewards: list[float] = field(default_factory=list)
done: bool = False
total_reward: float = 0.0
def collect_rollout(env: CrmAgentEnv, agent, seed: int | None = None) -> Rollout:
"""Collect a single rollout from the environment."""
rollout = Rollout()
# Reset environment
result = env.reset(seed=seed)
rollout.observations.append(result.observation)
while not result.done:
# Get action from agent (returns tool call dict)
tool_call = agent.get_action(
system_prompt=env.system_prompt,
tools=env.tools,
observation=result.observation,
)
# Parse and execute
parsed = env.parse_tool_call(tool_call)
if parsed.is_valid:
result = env.step(parsed.action)
reward = result.reward if result.reward is not None else 0.0
else:
# Invalid action penalty
reward = -1.0
result.done = True
# Store transition
rollout.actions.append(tool_call)
rollout.rewards.append(reward)
rollout.observations.append(result.observation)
rollout.done = True
rollout.total_reward = sum(rollout.rewards)
return rollout
# Collect multiple rollouts for training
def collect_rollouts(
env: CrmAgentEnv,
agent,
num_rollouts: int,
seed_offset: int = 0,
) -> list[Rollout]:
"""Collect multiple rollouts for batch training."""
rollouts = []
for i in range(num_rollouts):
rollout = collect_rollout(env, agent, seed=seed_offset + i)
rollouts.append(rollout)
return rollouts
# Example usage
with CrmAgentEnv(
base_url="https://env.crm.construct-labs.com",
api_key="your-api-key"
) as env:
# Collect 10 rollouts
rollouts = collect_rollouts(env, your_agent, num_rollouts=10)
# Compute statistics
avg_reward = sum(r.total_reward for r in rollouts) / len(rollouts)
avg_length = sum(len(r.actions) for r in rollouts) / len(rollouts)
print(f"Average reward: {avg_reward:.2f}")
print(f"Average episode length: {avg_length:.1f}")
GRPO Training Integration
For Group Relative Policy Optimization (GRPO) training:
from construct_labs_crm_env import CrmAgentEnv
def collect_grpo_group(
env: CrmAgentEnv,
agent,
group_size: int = 8,
seed: int = 0,
) -> list[Rollout]:
"""Collect a group of rollouts with the same seed for GRPO."""
group = []
for _ in range(group_size):
# Same seed = same initial state, different agent samples
rollout = collect_rollout(env, agent, seed=seed)
group.append(rollout)
return group
def compute_grpo_advantages(group: list[Rollout]) -> list[float]:
"""Compute relative advantages within a group."""
rewards = [r.total_reward for r in group]
mean_reward = sum(rewards) / len(rewards)
std_reward = (sum((r - mean_reward) ** 2 for r in rewards) / len(rewards)) ** 0.5
if std_reward < 1e-8:
return [0.0] * len(rewards)
return [(r - mean_reward) / std_reward for r in rewards]
# Training loop
with CrmAgentEnv(
base_url="https://env.crm.construct-labs.com",
api_key="your-api-key"
) as env:
for step in range(num_training_steps):
# Collect group of rollouts
group = collect_grpo_group(env, agent, group_size=8, seed=step)
# Compute advantages
advantages = compute_grpo_advantages(group)
# Update policy using advantages
agent.update(group, advantages)
Basic Training Loop
from construct_labs_crm_env import CrmAgentEnv
env = CrmAgentEnv(
base_url="https://env.crm.construct-labs.com",
api_key="your-api-key"
)
with env:
result = env.reset(seed=42)
while not result.done:
# Get action from your agent/LLM
tool_call = your_agent.get_action(
env.system_prompt,
env.tools,
result.observation
)
# Parse and execute
parsed = env.parse_tool_call(tool_call)
if parsed.is_valid:
result = env.step(parsed.action)
else:
# Handle invalid action
print(f"Invalid action: {parsed.error_message}")
break
API Reference
CrmAgentEnv
Main client class for interacting with the CRM environment.
Constructor:
base_url(str): Base URL of the CRM environment serverapi_key(str, optional): API key for authenticationconnect_timeout_s(float): Connection timeout in seconds (default: 10)message_timeout_s(float): Message timeout in seconds (default: 60)
Methods:
reset(seed=None)- Reset the environmentstep(action)- Execute an actionstate()- Get current environment stateclose()- Close the connectionparse_tool_call(tool_call)- Parse LLM tool call to actionformat_observation(observation)- Format observation for LLM
Properties (overridable):
system_prompt- System prompt for the agenttools- Available tool definitions
CrmAgentAction
Pydantic model for CRM actions.
action = CrmAgentAction(
action_type=CRMActionType.CREATE_COMPANY,
company_name="Acme Corp",
company_domain="acme.com",
company_employees=100
)
CrmAgentObservation
Pydantic model for CRM observations.
success(bool): Whether the action succeedederror(str | None): Error message if faileddata(dict): Raw response datadone(bool): Whether episode has endedreward(float | None): Reward signal
Use observation.model_dump_json() to get JSON representation.
Support
For licensing, technical support, or questions:
Email: hello@construct-labs.com
License
Copyright (c) 2024 Construct Labs GmbH. All rights reserved.
This software is proprietary and requires a commercial license. See LICENSE for details.
Metadata
Release files for construct-labs-crm-env 0.1.10
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| construct_labs_crm_env-0.1.10.tar.gz | 21.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| construct_labs_crm_env-0.1.10-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 47.2 kB
Release files / construct_labs_crm_env-0.1.10.tar.gz
| Download URL | construct_labs_crm_env-0.1.10.tar.gz |
|---|---|
| Size | 21.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fa3f5442528f84fbfc562de18c2b53f68a408cf08ffc14612c6480c5cbc9f67f
|
|
BLAKE2b-256 checksum How to use checksums |
8ce5c2ab1e8e3882c9a4f7e5e86ac51466f413ee53bfb4862fb34f192b87549c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 4, 2026.
Transparency logRelease files / construct_labs_crm_env-0.1.10-py3-none-any.whl
| Download URL | construct_labs_crm_env-0.1.10-py3-none-any.whl |
|---|---|
| Size | 25.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
173cf4a8ee6c362693d09043f8a971730ab535804fa00a2ccc5878bef3f3168b
|
|
BLAKE2b-256 checksum How to use checksums |
b0c7888e272bfb372b34a2be67c21dcbb1e175d690cc745d50a2e560b393422b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Feb 4, 2026.
Transparency log