Skip to content
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

Skill library - Skill Assistant mechanics #297

Merged
merged 14 commits into from
Jan 8, 2025
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
57 changes: 6 additions & 51 deletions assistants/skill-assistant/assistant/assistant_registry.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import asyncio
from os import PathLike
from pathlib import Path
from typing import Optional

from openai_client.chat_driver import ChatDriverConfig
from skill_library import Assistant, Skill
from skill_library import Assistant

from .logging import extra_data, logger
from .skill_event_mapper import SkillEventMapperProtocol
Expand All @@ -22,32 +18,6 @@ def __init__(self) -> None:
self.assistants: dict[str, Assistant] = {}
# self.tasks: set[asyncio.Task] = set()

async def get_or_register_assistant(
self,
assistant_id: str,
event_mapper: SkillEventMapperProtocol,
chat_driver_config: ChatDriverConfig,
assistant_name: str = "Assistant",
skills: Optional[dict[str, "Skill"]] = None,
drive_root: PathLike | None = None,
metadata_drive_root: PathLike | None = None,
) -> Assistant:
"""
Get or create an assistant for the given conversation context.
"""
assistant = self.get_assistant(assistant_id)
if not assistant:
assistant = await self.register_assistant(
assistant_id,
event_mapper,
chat_driver_config,
assistant_name,
skills,
drive_root,
metadata_drive_root,
)
return assistant

def get_assistant(
self,
assistant_id: str,
Expand All @@ -58,38 +28,23 @@ def get_assistant(

async def register_assistant(
self,
assistant_id: str,
assistant: Assistant,
event_mapper: SkillEventMapperProtocol,
chat_driver_config: ChatDriverConfig,
assistant_name: str = "Assistant",
skills: dict[str, Skill] | None = None,
drive_root: PathLike | None = None,
metadata_drive_root: PathLike | None = None,
) -> Assistant:
"""
Define the skill assistant that you want to have backing this assistant
service. You can configure the assistant instructions and which skills
to include here.
"""

logger.debug("Registering assistant.", extra_data({"assistant_id": assistant_id}))

# Create the assistant.
assistant = Assistant(
name=assistant_name,
assistant_id=assistant_id,
drive_root=drive_root or Path(".data") / assistant_id / "assistant",
metadata_drive_root=metadata_drive_root or Path(".data") / assistant_id / ".assistant",
chat_driver_config=chat_driver_config,
skills=skills,
)
logger.debug("Registering assistant.", extra_data({"assistant_id": assistant.assistant_id}))

# Assistant event consumer.
async def subscribe() -> None:
"""Event consumer for the assistant."""
logger.debug(
"Assistant event subscription started in the assistant registry.",
extra_data({"assistant_id": assistant_id}),
extra_data({"assistant_id": assistant.assistant_id}),
)
async for skill_event in assistant.events:
logger.debug(
Expand All @@ -104,11 +59,11 @@ async def subscribe() -> None:
await assistant.wait()
logger.debug(
"Assistant event subscription stopped in the assistant registry.",
extra_data({"assistant_id": assistant_id}),
extra_data({"assistant_id": assistant.assistant_id}),
)

# Register the assistant.
self.assistants[assistant_id] = assistant
self.assistants[assistant.assistant_id] = assistant

# Start an event consumer task and save a reference.
# task = asyncio.create_task(subscribe())
Expand Down
7 changes: 4 additions & 3 deletions assistants/skill-assistant/assistant/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ def default(self, o):
return super().default(o)


def extra_data(data: Any) -> dict[str, Any]:
def add_serializable_data(data: Any) -> dict[str, Any]:
"""
Helper function to use when adding extra data to log messages.
Helper function to use when adding extra data to log messages. Data will
attempt to be put into a serializable format.
"""
extra = {}

Expand All @@ -58,4 +59,4 @@ def extra_data(data: Any) -> dict[str, Any]:
return extra


extra_data = extra_data
extra_data = add_serializable_data
66 changes: 41 additions & 25 deletions assistants/skill-assistant/assistant/skill_assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@

import openai_client
from assistant_drive import Drive, DriveConfig
from common_skill import CommonSkillDefinition
from content_safety.evaluators import CombinedContentSafetyEvaluator
from form_filler_skill import FormFillerSkill
from guided_conversation_skill import GuidedConversationSkill
from guided_conversation_skill import GuidedConversationSkillDefinition
from openai_client.chat_driver import ChatDriverConfig
from posix_skill import PosixSkill
from posix_skill import PosixSkillDefinition
from semantic_workbench_api_model.workbench_model import (
ConversationEvent,
ConversationMessage,
Expand All @@ -30,6 +30,7 @@
ContentSafetyEvaluator,
ConversationContext,
)
from skill_library import Assistant
from skill_library.types import Metadata

from assistant.skill_event_mapper import SkillEventMapper
Expand Down Expand Up @@ -58,8 +59,8 @@


# Create the content safety interceptor.
async def content_evaluator_factory(context: ConversationContext) -> ContentSafetyEvaluator:
config = await assistant_config.get(context.assistant)
async def content_evaluator_factory(conversation_context: ConversationContext) -> ContentSafetyEvaluator:
config = await assistant_config.get(conversation_context.assistant)
return CombinedContentSafetyEvaluator(config.content_safety_config)


Expand Down Expand Up @@ -178,7 +179,23 @@ async def respond_to_conversation(
"""
Respond to a conversation message.
"""
assistant = await get_or_register_assistant(conversation_context, config)
try:
await assistant.put_message(message.content, metadata)
except Exception as e:
logger.exception("Exception in on_message_created.")
await conversation_context.send_messages(
NewConversationMessage(
message_type=MessageType.note,
content=f"Unhandled error: {e}",
)
)


# Get or register an assistant for the conversation.
async def get_or_register_assistant(
conversation_context: ConversationContext, config: AssistantConfigModel
) -> Assistant:
# Get an assistant from the registry.
assistant_id = conversation_context.id
assistant = assistant_registry.get_assistant(assistant_id)
Expand All @@ -194,26 +211,32 @@ async def respond_to_conversation(
model=config.chat_driver_config.openai_model,
instructions=config.chat_driver_config.instructions,
)
assistant = await assistant_registry.register_assistant(

assistant = Assistant(
assistant_id=conversation_context.id,
assistant_name="Assistant",
event_mapper=SkillEventMapper(conversation_context),
name="Assistant",
chat_driver_config=chat_driver_config,
drive_root=assistant_drive_root,
metadata_drive_root=assistant_metadata_drive_root,
skills={
"posix": PosixSkill(
"common": CommonSkillDefinition(
name="common",
language_model=language_model,
drive=assistant_drive.subdrive("common"),
chat_driver_config=chat_driver_config,
),
"posix": PosixSkillDefinition(
name="posix",
sandbox_dir=Path(".data") / conversation_context.id,
chat_driver_config=chat_driver_config,
mount_dir="/mnt/data",
),
"form_filler": FormFillerSkill(
name="form_filler",
chat_driver_config=chat_driver_config,
language_model=language_model,
),
"guided_conversation": GuidedConversationSkill(
# "form_filler": FormFillerSkill(
# name="form_filler",
# chat_driver_config=chat_driver_config,
# language_model=language_model,
# ),
"guided_conversation": GuidedConversationSkillDefinition(
name="guided_conversation",
language_model=language_model,
drive=assistant_drive.subdrive("guided_conversation"),
Expand All @@ -222,13 +245,6 @@ async def respond_to_conversation(
},
)

try:
await assistant.put_message(message.content, metadata)
except Exception as e:
logger.exception("Exception in on_message_created.")
await conversation_context.send_messages(
NewConversationMessage(
message_type=MessageType.note,
content=f"Unhandled error: {e}",
)
)
await assistant_registry.register_assistant(assistant, SkillEventMapper(conversation_context))

return assistant
5 changes: 3 additions & 2 deletions assistants/skill-assistant/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ dependencies = [
"azure-core[aio]>=1.30.0",
"azure-identity>=1.16.0",
"content-safety>=0.1.0",
"context>=0.1.0",
"common-skill>=0.1.0",
# "document-skill>=0.1.0",
"form-filler-skill>=0.1.0",
"guided-conversation-skill>=0.1.0",
"openai-client>=0.1.0",
"openai>=1.3.9",
"posix-skill>=0.1.0",
"semantic-workbench-assistant>=0.1.0",
"bs4>=0.0.2",
]

[dependency-groups]
Expand All @@ -34,8 +35,8 @@ package = true

[tool.uv.sources]
content-safety = { path = "../../libraries/python/content-safety", editable = true }
context = { path = "../../libraries/python/context", editable = true }
# document-skill = { path = "../../libraries/python/skills/skills/document-skill", editable = true }
common-skill = { path = "../../libraries/python/skills/skills/common-skill", editable = true }
form-filler-skill = { path = "../../libraries/python/skills/skills/form-filler-skill", editable = true }
guided-conversation-skill = { path = "../../libraries/python/skills/skills/guided-conversation-skill", editable = true }
openai-client = { path = "../../libraries/python/openai-client", editable = true }
Expand Down
71 changes: 69 additions & 2 deletions assistants/skill-assistant/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions libraries/python/openai-client/openai_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
)
from .logging import (
add_serializable_data,
extra_data,
make_completion_args_serializable,
)
from .messages import (
Expand Down Expand Up @@ -48,6 +49,7 @@
"create_assistant_message",
"create_system_message",
"create_user_message",
"extra_data",
"format_with_dict",
"format_with_liquid",
"make_completion_args_serializable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def register_function(self, function: Callable) -> None:

def register_functions(self, functions: list[Callable]) -> None:
for function in functions:
self.register_function
self.register_function(function)

# Sometimes we want to register a function to be used by both the user and
# the model.
Expand Down
Loading
Loading