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

Adds state_updated_event_after #216

Merged
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
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import contextlib
import json
from pathlib import Path
from typing import AsyncIterator, Callable
from typing import Callable

import yaml
from semantic_workbench_api_model.workbench_model import AssistantStateEvent
from semantic_workbench_assistant.assistant_app.context import ConversationContext
from semantic_workbench_assistant.assistant_app.protocol import (
AssistantConversationInspectorStateDataModel,
Expand Down Expand Up @@ -53,12 +52,3 @@ def read_state(path: Path) -> dict:
return AssistantConversationInspectorStateDataModel(
data={"content": f"```yaml\n{yaml.dump(state, sort_keys=False)}\n```"},
)


@contextlib.asynccontextmanager
async def state_change_event_after(context: ConversationContext, state_id: str, set_focus=False) -> AsyncIterator[None]:
"""Raise a state change event after the context manager block is executed (optionally set focus as well)"""
yield
if set_focus:
await context.send_conversation_state_event(AssistantStateEvent(state_id=state_id, event="focus", state=None))
await context.send_conversation_state_event(AssistantStateEvent(state_id=state_id, event="updated", state=None))
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from semantic_workbench_assistant.assistant_app.context import ConversationContext, storage_directory_for_context
from semantic_workbench_assistant.storage import read_model, write_model

from .inspector import FileStateInspector, state_change_event_after
from .inspector import FileStateInspector


class FormField(BaseModel):
Expand Down Expand Up @@ -55,7 +55,7 @@ async def agent_state(context: ConversationContext) -> AsyncIterator[FormFillAge
yield state
return

async with state_change_event_after(context, inspector.state_id):
async with context.state_updated_event_after(inspector.state_id):
state = read_model(path_for_state(context), FormFillAgentState) or FormFillAgentState()
current_state.set(state)
yield state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_workbench_assistant.assistant_app.context import ConversationContext, storage_directory_for_context

from ..inspector import state_change_event_after
from .types import GuidedConversationDefinition

_state_locks: dict[Path, asyncio.Lock] = defaultdict(asyncio.Lock)
Expand All @@ -41,7 +40,7 @@ async def engine(
given state file.
"""

async with _state_locks[state_file_path], state_change_event_after(context, state_id, set_focus=True):
async with _state_locks[state_file_path], context.state_updated_event_after(state_id, focus_event=True):
kernel, service_id = _build_kernel_with_service(openai_client, openai_model)

state: dict | None = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def _get_state_file_path(context: ConversationContext) -> Path:
"When providing options for a multiple choice field, provide the options in a numbered-list, so the user can refer to them by number.",
"When listing anything other than options, like document types, provide them in a bulleted list for improved readability.",
"When updating the agenda, the data-collection for each form field must be in a separate step.",
"When asking for data to fill the form, always ask for a single piece of information at a time. Never ask for multiple pieces of information in a single prompt, ex: 'Please provide field Y, and additionally, field X'.",
"Terminate conversation if inappropriate content is requested.",
],
conversation_flow="""
Expand All @@ -137,7 +138,6 @@ def _get_state_file_path(context: ConversationContext) -> Path:
4. When asking for files, suggest types of documents that might contain the data.
5. For each field in the form, check if the data is available in the provided files.
6. If the data is not available in the files, ask the user for the data.
7. When asking for data to fill the form, ask for a single piece of information at a time.
8. When the form is filled out, inform the user that you will now generate a document containing the filled form.
""",
context="",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,32 @@ async def file_exists(self, filename: str) -> bool:
async def delete_file(self, filename: str) -> None:
return await self._workbench_client.delete_file(filename)

@asynccontextmanager
async def state_updated_event_after(self, state_id: str, focus_event: bool = False) -> AsyncIterator[None]:
"""
Raise state "updated" event after the context manager block is executed, and optionally, a
state "focus" event.

Example:
```python
# notify workbench that state has been updated
async with conversation.state_updated_event_after("my_state_id"):
await do_some_work()

# notify workbench that state has been updated and set focus
async with conversation.state_updated_event_after("my_state_id", focus_event=True):
await do_some_work()
```
"""
yield
if focus_event:
await self.send_conversation_state_event(
workbench_model.AssistantStateEvent(state_id=state_id, event="focus", state=None)
)
await self.send_conversation_state_event(
workbench_model.AssistantStateEvent(state_id=state_id, event="updated", state=None)
)


def storage_directory_for_context(context: AssistantContext | ConversationContext, partition: str = "") -> pathlib.Path:
match context:
Expand Down