Skip to content

Commit

Permalink
Removes "assistant instance" terminology (#262)
Browse files Browse the repository at this point in the history
In favor of just "assistant"

Additionally:
- removes unused workflow code and DB tables
- adds participant.metadata for storing conversation state per user
  • Loading branch information
markwaddle authored Nov 25, 2024
1 parent 0dd996f commit 31d5f72
Show file tree
Hide file tree
Showing 26 changed files with 175 additions and 2,220 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/workbench-service.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: ["main"]
paths:
- "workbench-service/**"
- "libraries/python/semantic-workbench-assistant/**"
- "libraries/python/semantic-workbench-api-model/**"
- "tools/docker/**"
- ".github/workflows/workbench-service.yml"
Expand All @@ -13,6 +14,7 @@ on:
branches: ["main"]
paths:
- "workbench-service/**"
- "libraries/python/semantic-workbench-assistant/**"
- "libraries/python/semantic-workbench-api-model/**"
- "tools/docker/**"
- ".github/workflows/workbench-service.yml"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Workspace files allow us to manage multiple projects within a monorepo more effe
- Bring your own llm api keys
- Use VS Code > `Run and Debug` (Ctrl/Cmd+Shift+D) > `examples: python-02-simple-chatbot` to start the example chatbot assistant. Either set your keys in your .env file or after creating the assistant as described below, select it and provide the keys in the configuration page.

## Open the Workbench and create an Assistant instance
## Open the Workbench and create an Assistant

Open the app in your browser at [`https://localhost:4000`](https://localhost:4000). When you first log into the Semantic Workbench, follow these steps to get started:

Expand Down
4 changes: 2 additions & 2 deletions assistants/skill-assistant/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ The Skill Assistant serves as a demonstration of integrating the Skill Library w

## Overview

[skill_controller.py](assistant/skill_controller.py) file is responsible for managing the assistant instances. It includes functionality to create and retrieve assistants, configure chat drivers, and map skill events to the Semantic Workbench.
[skill_controller.py](assistant/skill_controller.py) file is responsible for managing the assistants. It includes functionality to create and retrieve assistants, configure chat drivers, and map skill events to the Semantic Workbench.

- AssistantRegistry: Manages multiple assistant instances, each associated with a unique conversation.
- AssistantRegistry: Manages multiple assistants, each associated with a unique conversation.
- \_event_mapper: Maps skill events to message types understood by the Semantic Workbench.
- create_assistant: Defines how to create and configure a new assistant.

Expand Down
8 changes: 4 additions & 4 deletions assistants/skill-assistant/assistant/assistant_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
# TODO: Put this registry in the skill library.
class AssistantRegistry:
"""
This class handles the creation and management of skill assistant instances
for this service. Each conversation has its own assistant and we start each
assistant in it's own thread so that all events are able to be
asynchronously passed on to the Semantic Workbench.
This class handles the creation and management of skill assistants for this service.
Each conversation has its own assistant and we start each assistant in it's own
thread so that all events are able to be asynchronously passed on to the Semantic
Workbench.
"""

def __init__(self) -> None:
Expand Down
12 changes: 6 additions & 6 deletions docs/WORKBENCH_APP.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ The Semantic Workbench interface dashboard includes sections for your assistants

Select any of your assistants to access and update the assistant's configuration. Select an existing conversation or create a new one to start interacting with your assistants.

# Assistant Instances
# Assistants

Creating and configuring assistants in the Semantic Workbench allows you to utilize different AI functionalities tailored to specific needs. Note that you can create multiple instances of a single assistant service, each with its own configuration.
Creating and configuring assistants in the Semantic Workbench allows you to utilize different AI functionalities tailored to specific needs. Note that you can create multiple assistants, all back by a single assistant service, each with its own configuration.

## Creating a New Assistant

Creating a new assistant in the Semantic Workbench is straightforward. Begin by clicking on the `New Assistant` button on the dashboard. You will be presented with the available assistant services to choose from. Select the one that best suits your needs. You can accept the default name or choose your own then click `Save` to create the instance.
Creating a new assistant in the Semantic Workbench is straightforward. Begin by clicking on the `New Assistant` button on the dashboard. You will be presented with the available assistant services to choose from. Select the one that best suits your needs. You can accept the default name or choose your own then click `Save` to create the assistant.

## Configuring Assistants

Expand Down Expand Up @@ -91,7 +91,7 @@ You have the ability to invite additional people to either observe or participat

## Creating a New Conversation

To start a new conversation with your assistant, click on its instance and then click `New Conversation`. Provide a title for the conversation and click `Save`.
To start a new conversation with your assistant, click on it and then click `New Conversation`. Provide a title for the conversation and click `Save`.

## Basics of Conversations

Expand Down Expand Up @@ -152,11 +152,11 @@ When you provide someone with a link to copy a conversation it will copy the con
* Second person follows the link later, they get the additional messages and data you added before they followed the link.

### Duplicating conversation
From the conversation list you can also duplicate them. This is useful for experimenting with taking conversations in different directions, or using one as a common base for further explorations.
From the conversation list you can also duplicate them. This is useful for experimenting with taking conversations in different directions, or using one as a common base for further explorations.

![Duplicate Conversation](images/conversation_duplicate.png)

Note that this will also copy the assistant instance the conversation is a part of as there is some state associated between the assistant and the conversation.
Note that this will also copy the assistant that the conversation is a part of as there is some state associated between the assistant and the conversation.

### Exporting and importing conversations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def __init__(
)


class AssistantInstanceClient:
class AssistantClient:
def __init__(self, httpx_client_factory: Callable[[], httpx.AsyncClient]) -> None:
self._client = httpx_client_factory()

Expand Down Expand Up @@ -157,7 +157,7 @@ async def put_config(self, updated_config: ConfigPutRequestModel) -> ConfigRespo
return ConfigResponseModel.model_validate(http_response.json())

@asynccontextmanager
async def get_exported_instance_data(self) -> AsyncGenerator[AsyncIterator[bytes], Any]:
async def get_exported_data(self) -> AsyncGenerator[AsyncIterator[bytes], Any]:
try:
http_response = await self._client.send(self._client.build_request("GET", "/export-data"), stream=True)
except httpx.RequestError as e:
Expand Down Expand Up @@ -266,7 +266,7 @@ async def __aexit__(
async def aclose(self) -> None:
await self._client.aclose()

async def put_assistant_instance(
async def put_assistant(
self,
assistant_id: uuid.UUID,
request: AssistantPutRequestModel,
Expand All @@ -284,7 +284,7 @@ async def put_assistant_instance(
if not response.is_success:
raise AssistantResponseError(response)

async def delete_assistant_instance(self, assistant_id: uuid.UUID) -> None:
async def delete_assistant(self, assistant_id: uuid.UUID) -> None:
try:
response = await self._client.delete(f"/{assistant_id}")
if response.status_code == httpx.codes.NOT_FOUND:
Expand Down Expand Up @@ -331,7 +331,7 @@ def _client(self, *additional_paths: str) -> httpx.AsyncClient:
def for_service(self) -> AssistantServiceClient:
return AssistantServiceClient(httpx_client_factory=self._client)

def for_assistant_instance(self, assistant_id: uuid.UUID) -> AssistantInstanceClient:
return AssistantInstanceClient(
def for_assistant(self, assistant_id: uuid.UUID) -> AssistantClient:
return AssistantClient(
httpx_client_factory=lambda: self._client(str(assistant_id)),
)
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@ class ParticipantRole(StrEnum):
service = "service"


class ParticipantStatus(BaseModel):
timestamp: datetime.datetime = Field(default_factory=lambda: datetime.datetime.now(datetime.UTC))
message: str | None = None


class ConversationPermission(StrEnum):
read_write = "read_write"
read = "read"
Expand All @@ -84,6 +79,7 @@ class ConversationParticipant(BaseModel):
active_participant: bool
online: bool | None = None
conversation_permission: ConversationPermission
metadata: dict[str, Any]


class ConversationParticipantList(BaseModel):
Expand Down Expand Up @@ -459,6 +455,7 @@ class UpdateParticipant(BaseModel):

status: str | None = None
active_participant: bool | None = None
metadata: dict[str, Any] | None = None


class ConversationEventType(StrEnum):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@ def from_headers(headers: Mapping[str, str]) -> AssistantServiceRequestHeaders:


@dataclass
class AssistantInstanceRequestHeaders:
class AssistantRequestHeaders:
assistant_id: uuid.UUID | None

def to_headers(self) -> Mapping[str, str]:
return {HEADER_ASSISTANT_ID: str(self.assistant_id)}

@staticmethod
def from_headers(headers: Mapping[str, str]) -> AssistantInstanceRequestHeaders:
def from_headers(headers: Mapping[str, str]) -> AssistantRequestHeaders:
assistant_id: uuid.UUID | None = None
with suppress(ValueError):
assistant_id = uuid.UUID(headers.get(HEADER_ASSISTANT_ID) or "")
return AssistantInstanceRequestHeaders(
return AssistantRequestHeaders(
assistant_id=assistant_id,
)

Expand Down Expand Up @@ -480,7 +480,7 @@ def __init__(
self._assistant_service_id = assistant_service_id
self._api_key = api_key

def _client(self, *headers: AssistantServiceRequestHeaders | AssistantInstanceRequestHeaders) -> httpx.AsyncClient:
def _client(self, *headers: AssistantServiceRequestHeaders | AssistantRequestHeaders) -> httpx.AsyncClient:
client = httpx.AsyncClient(
transport=httpx_transport_factory(),
base_url=self._base_url,
Expand All @@ -505,7 +505,7 @@ def for_conversation(self, assistant_id: str, conversation_id: str) -> Conversat
conversation_id=conversation_id,
httpx_client_factory=lambda: self._client(
AssistantServiceRequestHeaders(assistant_service_id=self._assistant_service_id, api_key=self._api_key),
AssistantInstanceRequestHeaders(assistant_id=uuid.UUID(assistant_id)),
AssistantRequestHeaders(assistant_id=uuid.UUID(assistant_id)),
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,7 @@ async def get_service_description() -> assistant_model.ServiceInfoModel:
@app.put(
"/{assistant_id}",
description=(
"Connect an assistant instance to the workbench, optionally"
" providing exported-data to restore the assistant"
"Connect an assistant to the workbench, optionally providing exported-data to restore the assistant"
),
)
async def put_assistant(
Expand All @@ -328,21 +327,21 @@ async def put_assistant(

@app.get(
"/{assistant_id}",
description="Get an assistant instance",
description="Get an assistant",
)
async def get_assistant(assistant_id: str) -> assistant_model.AssistantResponseModel:
return await service.get_assistant(assistant_id)

@app.delete(
"/{assistant_id}",
description="Delete an assistant instance",
description="Delete an assistant",
)
async def delete_assistant(assistant_id: str) -> None:
return await service.delete_assistant(assistant_id)

@app.get(
"/{assistant_id}/export-data",
description="Export all data for this assistant instance",
description="Export all data for this assistant",
)
async def export_assistant_data(assistant_id: str) -> Response:
response = await service.export_assistant_data(assistant_id)
Expand All @@ -356,14 +355,14 @@ async def export_assistant_data(assistant_id: str) -> Response:

@app.get(
"/{assistant_id}/config",
description="Get config for this assistant instance",
description="Get config for this assistant",
)
async def get_config(assistant_id: str) -> assistant_model.ConfigResponseModel:
return await service.get_config(assistant_id)

@app.put(
"/{assistant_id}/config",
description="Set config for this assistant instance",
description="Set config for this assistant",
)
async def put_config(
assistant_id: str, updated_config: assistant_model.ConfigPutRequestModel
Expand All @@ -373,7 +372,7 @@ async def put_config(
@app.put(
"/{assistant_id}/conversations/{conversation_id}",
description=(
"Join an assistant instance to a workbench conversation, optionally"
"Join an assistant to a workbench conversation, optionally"
" providing exported-data to restore the conversation"
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,9 @@ async def on_chat_message(

client_builder = assistant_service_client.AssistantServiceClientBuilder("https://fake", "")
service_client = client_builder.for_service()
instance_client = client_builder.for_assistant_instance(assistant_id)
instance_client = client_builder.for_assistant(assistant_id)

await service_client.put_assistant_instance(
assistant_id=assistant_id, request=assistant_request, from_export=None
)
await service_client.put_assistant(assistant_id=assistant_id, request=assistant_request, from_export=None)

assert assistant_created_calls == 1

Expand Down Expand Up @@ -263,11 +261,9 @@ async def get(self, context: ConversationContext) -> AssistantConversationInspec

client_builder = assistant_service_client.AssistantServiceClientBuilder("https://fake", "")
service_client = client_builder.for_service()
instance_client = client_builder.for_assistant_instance(assistant_id)
instance_client = client_builder.for_assistant(assistant_id)

await service_client.put_assistant_instance(
assistant_id=assistant_id, request=assistant_request, from_export=None
)
await service_client.put_assistant(assistant_id=assistant_id, request=assistant_request, from_export=None)
await instance_client.put_conversation(
request=assistant_model.ConversationPutRequestModel(
id=str(conversation_id),
Expand Down Expand Up @@ -334,11 +330,9 @@ async def import_(self, conversation_context: ConversationContext, stream: IO[by

client_builder = assistant_service_client.AssistantServiceClientBuilder("https://fake", "")
service_client = client_builder.for_service()
instance_client = client_builder.for_assistant_instance(assistant_id)
instance_client = client_builder.for_assistant(assistant_id)

await service_client.put_assistant_instance(
assistant_id=assistant_id, request=assistant_request, from_export=None
)
await service_client.put_assistant(assistant_id=assistant_id, request=assistant_request, from_export=None)

conversation_id = uuid.uuid4()

Expand Down Expand Up @@ -415,11 +409,9 @@ class TestConfigModel(BaseModel):

client_builder = assistant_service_client.AssistantServiceClientBuilder("https://fake", "")
service_client = client_builder.for_service()
instance_client = client_builder.for_assistant_instance(assistant_id)
instance_client = client_builder.for_assistant(assistant_id)

await service_client.put_assistant_instance(
assistant_id=assistant_id, request=assistant_request, from_export=None
)
await service_client.put_assistant(assistant_id=assistant_id, request=assistant_request, from_export=None)

response = await instance_client.get_config()
assert response == assistant_model.ConfigResponseModel(
Expand Down Expand Up @@ -463,7 +455,7 @@ class TestConfigModel(BaseModel):
temp_dir_path = pathlib.Path(temp_dir)
export_file_path = temp_dir_path / "export.zip"
with export_file_path.open("wb") as f:
async with instance_client.get_exported_instance_data() as response:
async with instance_client.get_exported_data() as response:
async for chunk in response:
f.write(chunk)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export const AssistantCreate: React.FC<AssistantCreateProps> = (props) => {
}}
>
<DialogBody>
<DialogTitle>New Instance of Assistant</DialogTitle>
<DialogTitle>New Assistant</DialogTitle>
<DialogContent className={classes.dialogContent}>
{!manualEntry && (
<Field label="Assistant Service">
Expand Down
4 changes: 4 additions & 0 deletions workbench-app/src/models/ConversationParticipant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
export interface ConversationParticipant {
role: 'user' | 'assistant' | 'service';
id: string;
conversationId: string;
name: string;
image?: string;
online?: boolean;
status: string | null;
statusTimestamp: string | null;
conversationPermission: 'read' | 'read_write';
active: boolean;
metadata: {
[key: string]: any;
};
}
8 changes: 5 additions & 3 deletions workbench-app/src/services/workbench/participant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ const participantApi = workbenchApi.injectEndpoints({
}),
updateConversationParticipant: builder.mutation<
void,
{ conversationId: string; participantId: string; status: string }
{ conversationId: string; participantId: string; status?: string; metadata?: Record<string, any> }
>({
query: ({ conversationId, participantId, status }) => ({
query: ({ conversationId, participantId, status, metadata }) => ({
url: `/conversations/${conversationId}/participants/${participantId}`,
method: 'PUT',
body: { status, active_participant: true },
body: { status, active_participant: true, metadata },
}),
invalidatesTags: ['Conversation'],
}),
Expand Down Expand Up @@ -65,6 +65,7 @@ export const transformResponseToConversationParticipant = (response: any): Conve
try {
return {
id: response.id,
conversationId: response.conversation_id,
role: response.role,
name: response.name,
image: response.image ?? undefined,
Expand All @@ -73,6 +74,7 @@ export const transformResponseToConversationParticipant = (response: any): Conve
statusTimestamp: response.status_updated_timestamp,
conversationPermission: response.conversation_permission,
active: response.active_participant,
metadata: response.metadata,
};
} catch (error) {
throw new Error(`Failed to transform participant response: ${error}`);
Expand Down
Loading

0 comments on commit 31d5f72

Please sign in to comment.