Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,29 @@ pip install dimo-python-sdk

## Unit Testing

Coming Soon
The SDK includes comprehensive unit tests to ensure reliability and correctness. To run the tests:

1. **Install dependencies:**
```bash
pip install -r requirements.txt
```

2. **Run all tests:**
```bash
pytest
```

3. **Run tests with verbose output:**
```bash
pytest -v
```

4. **Run specific test files:**
```bash
pytest tests/test_conversations.py -v
```

The test suite uses `pytest` and includes tests for all major SDK functionality including authentication, API endpoints, GraphQL queries, and error handling

## API Documentation

Expand Down
51 changes: 33 additions & 18 deletions dimo/api/conversations.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,29 @@ def health_check(self) -> Dict:
def create_agent(
self,
developer_jwt: str,
user: str,
vehicle_ids: Optional[List[int]] = None,
api_key: str,
user_wallet: str,
agent_type: str,
vehicle_ids: Optional[str] = None,
personality: str = "uncle_mechanic",
) -> Dict:
"""
Create a new conversational agent for a user with optional vehicle access.
Create a new conversational agent with the specified configuration.

Args:
developer_jwt (str): Developer JWT token for authentication
user (str): Wallet address (0x...) or email identifying the user
vehicle_ids (list[int], optional): List of vehicle token IDs this agent can access.
- None (default): Unrestricted access, ownership validated at runtime
- []: Empty list means no vehicle access (identity queries only)
- [872, 1234]: Explicit list of allowed vehicles
api_key (str): DIMO API key for the agent to access vehicle data
user_wallet (str): User's wallet address (e.g., "0x2345...")
agent_type (str): The type of agent to create (e.g., "driver_agent_v1")
vehicle_ids (str, optional): JSON array string of vehicle token IDs (e.g., "[1, 2, 3]").
If not provided, agent will have access to all vehicles owned by the user.
personality (str, optional): Personality preset for the agent. Defaults to "uncle_mechanic"

Returns:
dict: Agent information including agentId, mode, user, vehicleIds, and createdAt
dict: Agent information including agentId and configuration details

Behavior:
- One agent per user (idempotent creation)
- Creates a new agent with the specified type and configuration
- Validates configuration and mode detection
- Creates/reuses shared identity subagent
- Creates per-vehicle telemetry subagents with token exchange
Expand All @@ -73,20 +77,31 @@ def create_agent(
>>> dev_jwt = "your_developer_jwt"
>>> agent = dimo.conversations.create_agent(
... developer_jwt=dev_jwt,
... user="0x1234567890abcdef1234567890abcdef12345678",
... vehicle_ids=[872, 1234],
... api_key="0x1234567890abcdef...",
... user_wallet="0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605",
... agent_type="driver_agent_v1",
... vehicle_ids="[1, 2, 3]",
... )
>>> print(agent['agentId'])
"""
check_type("developer_jwt", developer_jwt, str)
check_type("user", user, str)
check_optional_type("vehicle_ids", vehicle_ids, list)
# check_type("enable_websearch", enable_websearch, bool)
check_type("api_key", api_key, str)
check_type("user_wallet", user_wallet, str)
check_optional_type("vehicle_ids", vehicle_ids, str)
check_type("agent_type", agent_type, str)
check_type("personality", personality, str)

# Build variables dict
variables = {"USER_WALLET": user_wallet}
if vehicle_ids is not None:
variables["VEHICLE_IDS"] = vehicle_ids

# Build request body
body = {
"user": user,
"vehicleIds": vehicle_ids,
# "enableWebsearch": enable_websearch,
"personality": personality,
"secrets": {"DIMO_API_KEY": api_key},
"type": agent_type,
"variables": variables,
}

response = self._request(
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "dimo-python-sdk"
version = "1.7.0"
version = "1.7.1"
authors = [
{ name="Barrett Kowalsky", email="barrettkowalsky@gmail.com" },
]
Expand Down
150 changes: 113 additions & 37 deletions tests/test_conversations.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,27 @@ class TestConversationsCreateAgent:
"""Test the create_agent endpoint."""

def test_create_agent_minimal(self, monkeypatch):
"""Test creating an agent with minimal parameters."""
"""Test creating an agent with minimal required parameters (no vehicle_ids)."""
client = DIMO(env="Dev")

# Mock the request method
fake_request = MagicMock(return_value={
"agentId": "agent-abc123",
"user": "0x1234567890abcdef1234567890abcdef12345678",
"vehicleIds": None,
"mode": "unrestricted",
"type": "driver_agent_v1",
"personality": "uncle_mechanic",
"createdAt": "2024-01-01T00:00:00Z"
})
monkeypatch.setattr(client, "request", fake_request)

dev_jwt = "test_developer_jwt"
user = "0x1234567890abcdef1234567890abcdef12345678"
api_key = "0x1234567890abcdef"
user_wallet = "0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605"

result = client.conversations.create_agent(
developer_jwt=dev_jwt,
user=user
api_key=api_key,
user_wallet=user_wallet,
agent_type="driver_agent_v1"
)

# Verify the request was called correctly
Expand All @@ -75,66 +77,104 @@ def test_create_agent_minimal(self, monkeypatch):
assert args[0] == "POST"
assert args[1] == "Conversations"
assert args[2] == "/agents"
assert kwargs["data"]["user"] == user
assert kwargs["data"]["vehicleIds"] is None
assert kwargs["data"]["type"] == "driver_agent_v1"
assert kwargs["data"]["personality"] == "uncle_mechanic"
assert kwargs["data"]["secrets"]["DIMO_API_KEY"] == api_key
assert kwargs["data"]["variables"]["USER_WALLET"] == user_wallet
assert "VEHICLE_IDS" not in kwargs["data"]["variables"]

# Verify the response
assert result["agentId"] == "agent-abc123"
assert result["user"] == user
assert result["mode"] == "unrestricted"
assert result["type"] == "driver_agent_v1"

def test_create_agent_with_vehicle_ids(self, monkeypatch):
"""Test creating an agent with specific vehicle IDs."""
client = DIMO(env="Dev")

fake_request = MagicMock(return_value={
"agentId": "agent-def456",
"user": "user@example.com",
"vehicleIds": [872, 1234],
"mode": "restricted",
"type": "driver_agent_v1",
"createdAt": "2024-01-01T00:00:00Z"
})
monkeypatch.setattr(client, "request", fake_request)

dev_jwt = "test_developer_jwt"
user = "user@example.com"
vehicle_ids = [872, 1234]
api_key = "0xabcdef123456"
user_wallet = "0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605"
vehicle_ids = "[872, 1234]"

result = client.conversations.create_agent(
developer_jwt=dev_jwt,
user=user,
api_key=api_key,
user_wallet=user_wallet,
agent_type="driver_agent_v1",
vehicle_ids=vehicle_ids
)

# Verify the request
args, kwargs = fake_request.call_args
assert kwargs["data"]["vehicleIds"] == [872, 1234]
assert kwargs["data"]["secrets"]["DIMO_API_KEY"] == api_key
assert kwargs["data"]["variables"]["USER_WALLET"] == user_wallet
assert kwargs["data"]["variables"]["VEHICLE_IDS"] == vehicle_ids

# Verify the response
assert result["vehicleIds"] == vehicle_ids
assert result["mode"] == "restricted"
assert result["agentId"] == "agent-def456"

def test_create_agent_with_empty_vehicle_list(self, monkeypatch):
"""Test creating an agent with empty vehicle list (identity only)."""
def test_create_agent_with_custom_personality(self, monkeypatch):
"""Test creating an agent with custom personality preset."""
client = DIMO(env="Dev")

fake_request = MagicMock(return_value={
"agentId": "agent-ghi789",
"user": "0xabcdef",
"vehicleIds": [],
"mode": "identity_only",
"type": "driver_agent_v1",
"personality": "helpful_assistant",
"createdAt": "2024-01-01T00:00:00Z"
})
monkeypatch.setattr(client, "request", fake_request)

result = client.conversations.create_agent(
developer_jwt="test_jwt",
user="0xabcdef",
vehicle_ids=[]
api_key="0xapikey",
user_wallet="0xwallet",
agent_type="driver_agent_v1",
personality="helpful_assistant"
)

assert result["vehicleIds"] == []
assert result["mode"] == "identity_only"
# Verify the request
args, kwargs = fake_request.call_args
assert kwargs["data"]["personality"] == "helpful_assistant"

# Verify the response
assert result["personality"] == "helpful_assistant"

def test_create_agent_full_config(self, monkeypatch):
"""Test creating an agent with all configuration options."""
client = DIMO(env="Dev")

fake_request = MagicMock(return_value={
"agentId": "agent-full123",
"type": "driver_agent_v1",
"personality": "uncle_mechanic",
"createdAt": "2024-01-01T00:00:00Z"
})
monkeypatch.setattr(client, "request", fake_request)

result = client.conversations.create_agent(
developer_jwt="test_jwt",
api_key="0x1234567890abcdef",
user_wallet="0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605",
vehicle_ids="[1, 2, 3]",
agent_type="driver_agent_v1",
personality="uncle_mechanic"
)

# Verify all fields are in request
args, kwargs = fake_request.call_args
assert kwargs["data"]["type"] == "driver_agent_v1"
assert kwargs["data"]["personality"] == "uncle_mechanic"
assert kwargs["data"]["secrets"]["DIMO_API_KEY"] == "0x1234567890abcdef"
assert kwargs["data"]["variables"]["USER_WALLET"] == "0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605"
assert kwargs["data"]["variables"]["VEHICLE_IDS"] == "[1, 2, 3]"

def test_create_agent_invalid_types(self):
"""Test that type checking is enforced for parameters."""
Expand All @@ -144,22 +184,56 @@ def test_create_agent_invalid_types(self):
with pytest.raises(DimoTypeError):
client.conversations.create_agent(
developer_jwt=123, # Should be string
user="0xabcdef"
api_key="0xapikey",
user_wallet="0xwallet",
agent_type="driver_agent_v1"
)

# Test invalid api_key type
with pytest.raises(DimoTypeError):
client.conversations.create_agent(
developer_jwt="test_jwt",
api_key=123, # Should be string
user_wallet="0xwallet",
agent_type="driver_agent_v1"
)

# Test invalid user_wallet type
with pytest.raises(DimoTypeError):
client.conversations.create_agent(
developer_jwt="test_jwt",
api_key="0xapikey",
user_wallet=123, # Should be string
agent_type="driver_agent_v1"
)

# Test invalid user type
# Test invalid agent_type type
with pytest.raises(DimoTypeError):
client.conversations.create_agent(
developer_jwt="test_jwt",
user=123 # Should be string
api_key="0xapikey",
user_wallet="0xwallet",
agent_type=123 # Should be string
)

# Test invalid vehicle_ids type
with pytest.raises(DimoTypeError):
client.conversations.create_agent(
developer_jwt="test_jwt",
user="0xabcdef",
vehicle_ids="not_a_list" # Should be list or None
api_key="0xapikey",
user_wallet="0xwallet",
agent_type="driver_agent_v1",
vehicle_ids=123 # Should be string or None
)

# Test invalid personality type
with pytest.raises(DimoTypeError):
client.conversations.create_agent(
developer_jwt="test_jwt",
api_key="0xapikey",
user_wallet="0xwallet",
agent_type="driver_agent_v1",
personality=123 # Should be string
)


Expand Down Expand Up @@ -573,8 +647,8 @@ def fake_request(*args, **kwargs):
if args[0] == "POST" and args[2] == "/agents":
return {
"agentId": "agent-test123",
"user": "0xuser",
"vehicleIds": [872],
"type": "driver_agent_v1",
"personality": "uncle_mechanic",
"createdAt": "2024-01-01T00:00:00Z"
}
elif args[0] == "POST" and "/message" in args[2]:
Expand All @@ -592,8 +666,10 @@ def fake_request(*args, **kwargs):
# 1. Create agent
agent = client.conversations.create_agent(
developer_jwt="test_jwt",
user="0xuser",
vehicle_ids=[872]
api_key="0x1234567890abcdef",
user_wallet="0x86b04f6d1D9E79aD7eB31cDEAF37442B00d64605",
agent_type="driver_agent_v1",
vehicle_ids="[872]"
)
assert agent["agentId"] == "agent-test123"
assert ("POST", "/agents") in calls_made
Expand Down