-
Notifications
You must be signed in to change notification settings - Fork 698
feat: agent visibility and template improvements #934
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1c98fee
a43ac2a
1c638d5
bc93162
f73be7d
771313a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ | |
| from pydantic import BaseModel | ||
| from sqlalchemy import select | ||
|
|
||
| from intentkit.models.agent import Agent, AgentCore, AgentTable | ||
| from intentkit.models.agent import Agent, AgentCore, AgentTable, AgentVisibility | ||
| from intentkit.models.db import get_session | ||
| from intentkit.models.template import Template, TemplateTable | ||
|
|
||
|
|
@@ -120,6 +120,9 @@ class AgentCreationFromTemplate(BaseModel): | |
| name: str | None = None | ||
| picture: str | None = None | ||
| description: str | None = None | ||
| readonly_wallet_address: str | None = None | ||
| weekly_spending_limit: float | None = None | ||
| extra_prompt: str | None = None | ||
|
Comment on lines
+123
to
+125
|
||
|
|
||
|
|
||
| async def create_agent_from_template( | ||
|
|
@@ -158,13 +161,20 @@ async def create_agent_from_template( | |
| if data.picture: | ||
| core_data["picture"] = data.picture | ||
|
|
||
| # Set visibility based on team_id | ||
| visibility = AgentVisibility.TEAM if team_id else AgentVisibility.PRIVATE | ||
|
|
||
| # Create new agent | ||
| db_agent = AgentTable( | ||
| id=str(XID()), | ||
| owner=owner, | ||
| team_id=team_id, | ||
| template_id=data.template_id, | ||
| description=data.description, | ||
| readonly_wallet_address=data.readonly_wallet_address, | ||
| weekly_spending_limit=data.weekly_spending_limit, | ||
| extra_prompt=data.extra_prompt, | ||
| visibility=visibility, | ||
| **core_data, | ||
| ) | ||
| db.add(db_agent) | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |||||
| import warnings | ||||||
| from datetime import UTC, datetime | ||||||
| from decimal import Decimal | ||||||
| from enum import IntEnum | ||||||
| from pathlib import Path | ||||||
| from typing import Annotated, Any, Literal | ||||||
|
|
||||||
|
|
@@ -19,7 +20,7 @@ | |||||
| from pydantic import Field as PydanticField | ||||||
| from pydantic.json_schema import SkipJsonSchema | ||||||
| from pydantic.main import IncEx | ||||||
| from sqlalchemy import Boolean, DateTime, Float, Numeric, String, func, select | ||||||
| from sqlalchemy import Boolean, DateTime, Float, Integer, Numeric, String, func, select | ||||||
| from sqlalchemy.dialects.postgresql import JSON, JSONB | ||||||
| from sqlalchemy.exc import IntegrityError | ||||||
| from sqlalchemy.ext.asyncio import AsyncSession | ||||||
|
|
@@ -43,6 +44,20 @@ | |||||
| ) | ||||||
|
|
||||||
|
|
||||||
| class AgentVisibility(IntEnum): | ||||||
| """Agent visibility levels with hierarchical ordering. | ||||||
|
|
||||||
| Higher values indicate broader visibility: | ||||||
| - PRIVATE (0): Only visible to owner | ||||||
| - TEAM (10): Visible to team members | ||||||
| - PUBLIC (20): Visible to everyone | ||||||
| """ | ||||||
|
|
||||||
| PRIVATE = 0 | ||||||
| TEAM = 10 | ||||||
| PUBLIC = 20 | ||||||
|
|
||||||
|
|
||||||
| class AgentAutonomous(BaseModel): | ||||||
| """Autonomous agent configuration.""" | ||||||
|
|
||||||
|
|
@@ -458,6 +473,17 @@ class AgentTable(Base, AgentUserInputColumns): | |||||
| nullable=True, | ||||||
| comment="Price of the x402 request", | ||||||
| ) | ||||||
| visibility: Mapped[int | None] = mapped_column( | ||||||
| Integer, | ||||||
| nullable=True, | ||||||
| index=True, | ||||||
| comment="Visibility level: 0=private, 10=team, 20=public", | ||||||
| ) | ||||||
| archived_at: Mapped[datetime | None] = mapped_column( | ||||||
| DateTime(timezone=True), | ||||||
| nullable=True, | ||||||
| comment="Timestamp when the agent was archived. NULL means not archived", | ||||||
| ) | ||||||
|
|
||||||
| # auto timestamp | ||||||
| created_at: Mapped[datetime] = mapped_column( | ||||||
|
|
@@ -764,6 +790,28 @@ class AgentUpdate(AgentUserInput): | |||||
| }, | ||||||
| ), | ||||||
| ] | ||||||
| extra_prompt: Annotated[ | ||||||
| str | None, | ||||||
| PydanticField( | ||||||
| default=None, | ||||||
| description="Only when the agent is created from a template.", | ||||||
| max_length=20000, | ||||||
| ), | ||||||
| ] | ||||||
| visibility: Annotated[ | ||||||
| AgentVisibility | None, | ||||||
| PydanticField( | ||||||
| default=None, | ||||||
| description="Visibility level of the agent: PRIVATE(0), TEAM(10), or PUBLIC(20)", | ||||||
| ), | ||||||
| ] | ||||||
| archived_at: Annotated[ | ||||||
| datetime | None, | ||||||
| PydanticField( | ||||||
| default=None, | ||||||
| description="Timestamp when the agent was archived. NULL means not archived", | ||||||
| ), | ||||||
| ] | ||||||
|
|
||||||
| @field_validator("purpose", "personality", "principles", "prompt", "prompt_append") | ||||||
|
||||||
| @field_validator("purpose", "personality", "principles", "prompt", "prompt_append") | |
| @field_validator("purpose", "personality", "principles", "prompt", "prompt_append", "extra_prompt") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ | |
| create_template_from_agent, | ||
| render_agent, | ||
| ) | ||
| from intentkit.models.agent import Agent, AgentTable | ||
| from intentkit.models.agent import Agent, AgentTable, AgentVisibility | ||
| from intentkit.models.template import Template, TemplateTable | ||
|
|
||
|
|
||
|
|
@@ -76,6 +76,9 @@ async def mock_refresh(instance): | |
| assert added_agent.owner == "user_1" | ||
| assert added_agent.team_id == "team_1" | ||
|
|
||
| # Verify visibility is set to TEAM when team_id is provided | ||
| assert added_agent.visibility == AgentVisibility.TEAM | ||
|
|
||
| # Verify overrides | ||
| assert added_agent.name == "New Agent" | ||
| assert added_agent.picture == "new_pic.png" | ||
|
|
@@ -94,6 +97,67 @@ async def mock_refresh(instance): | |
| assert agent.id == added_agent.id | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_create_agent_from_template_without_team(): | ||
| """Test creating an agent from a template without team_id (PRIVATE visibility).""" | ||
|
|
||
| # 1. Setup Data | ||
| template_id = "template-2" | ||
| template_data = { | ||
| "id": template_id, | ||
| "name": "Template Agent", | ||
| "description": "A template agent", | ||
| "model": "gpt-4o", | ||
| "temperature": 0.5, | ||
| "prompt": "You are a template.", | ||
| } | ||
|
|
||
| # Mock TemplateTable instance | ||
| mock_template = TemplateTable(**template_data) | ||
|
|
||
| # Input data for creation | ||
| creation_data = AgentCreationFromTemplate( | ||
| template_id=template_id, | ||
| name="Private Agent", | ||
| picture="private_pic.png", | ||
| description="Created without team", | ||
| ) | ||
|
|
||
| # 2. Mock Database | ||
| with patch("intentkit.core.template.get_session") as mock_get_session: | ||
| # Setup mock session | ||
| mock_session = MagicMock() | ||
| mock_get_session.return_value.__aenter__.return_value = mock_session | ||
|
|
||
| # Mock scalar return value | ||
| mock_session.scalar = AsyncMock(return_value=mock_template) | ||
| mock_session.commit = AsyncMock() | ||
|
|
||
| # Set timestamps on refresh | ||
| async def mock_refresh(instance): | ||
| instance.created_at = datetime.now() | ||
| instance.updated_at = datetime.now() | ||
|
|
||
| mock_session.refresh = AsyncMock(side_effect=mock_refresh) | ||
|
|
||
| # 3. Call Function without team_id | ||
| await create_agent_from_template( | ||
| data=creation_data, owner="user_2", team_id=None | ||
| ) | ||
|
|
||
| # 4. Verify | ||
| assert mock_session.add.called | ||
| args, _ = mock_session.add.call_args | ||
| added_agent = args[0] | ||
|
|
||
| assert isinstance(added_agent, AgentTable) | ||
| assert added_agent.owner == "user_2" | ||
| assert added_agent.team_id is None | ||
|
|
||
| # Verify visibility is set to PRIVATE when team_id is None | ||
| assert added_agent.visibility == AgentVisibility.PRIVATE | ||
|
Comment on lines
+100
to
+158
|
||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_create_template_from_agent(): | ||
| """Test creating a template from an agent.""" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The volume mount path has been changed from a named volume to a host directory mount. The original path was "postgres_data:/var/lib/postgresql/data" (standard PostgreSQL data directory), but it's now "./pg_data:/var/lib/postgresql" which is the parent directory. This change may cause PostgreSQL to fail to start or lose data because:
This appears to be an unintended change that's unrelated to the PR's stated purpose of adding agent visibility features.