genie-flow-invoker-transfer 0.0.0.dev0
Genie Flow Invoker Transfer
Requires Python: >=3.11.0

# Transfer Invokers
[![PyPI version](https://badge.fury.io/py/genie-flow-invoker-transfer.svg?icon=si%3Apython)](https://badge.fury.io/py/genie-flow-invoker-transfer)
![PyPI - Downloads](https://img.shields.io/pypi/dm/genie-flow-invoker-transfer)

This package contains the Genie Flow to transfer a session to a new Genie Flow session and
pick up an existing session by sending in an event.

## Install

`pip install genie-flow-invoker-transfer`

## Usage
There are two separate invokers. One to start a new session with another agent and one to restart
a dialogue with another agent. The first (`StartGenieSessionInvoker`) is used to start a new
session and potentially send `seed_data` to the newly started session. The return value from
the new agent is returned as a result of the invocation. The second (`SendGenieEventInvoker`) is
used to send an event to another agent, passing in an existing session id and context data for
that event.

The pattern to transfer from AgentA to AgentB, and back, looks like:

```mermaid
sequenceDiagram
        actor User
        actor AgentA
        actor AgentB

        par User Converses with Agent A
        User->>AgentA: start_session
        AgentA->>User: {session_id_A, content, events}

        User->>AgentA: event(session_id_A, event, content)
        AgentA->>User: {session_id_A, content, events}
        end

        par AgentA transfers User to AgentB
        User->>AgentA: event(session_id_A, content, event)
        Note right of AgentA: StartGenieSessionInvoker
        AgentA->>AgentB: start_session(model_key, user_info, seed_data=<rendered template for invoker>)
        AgentB->>AgentB: call `seed_model` to seed session with new data
        AgentB->>AgentA: {session_id_B, content, events}
        AgentA->>User: Transfer{session_id_B, content, events}
        end

        par User Converses with AgentB
        User->>AgentB: event(session_id_B, event, content)
        AgentB->>User: {session_id_B, content, events}
        end

        par AgentB transfers User back to AgentA
        User->>AgentB: event(session_id_B, content, event)
        Note right of AgentB: SendGenieEventInvoker
        AgentB->>AgentA: start_event(model_key, session_id=session_id_B, event="some_event", content=<rendered template for invoker>)
        AgentA->>AgentB: {events}
        AgentB->>User: Transfer{session_id_A, content, events}
        end

        par User Converses with AgentA
        User->>AgentA: event(session_id_A, content, event)
        AgentA->>User: {session_id_A, content, events}
        end
```


## Configuration
The configurations that need to be made in the `meta.yaml` of the invoking templates. These
configurations can all be overridden in the template.

`target_url`
: the url where the target agent can be addressed. Is overridden by the environment variable
`GENIE_FLOW_BASE_URL`.

`taget_model_key`
: the model key of the target agent at the given `target_url`. There is no environment override
  for this property.

## Usage Considerations
The templates rendered by the _target_ agent (the "intro" template for a new session and whatever
template designed for the return event) is made available to the _calling_ agent as the 
`actor_input`.

### Starting a new session with another agent
The return value of the `StartGenieSessionInvoker` is a JSON-encoding of the `AIResponse`
object that gets returned from a `start_session` call. The `content` property of that response
is the rendered template. So, that is what the Invoker returns and what becomes available to
the agent as `actor_input` when it renders the template for the next state.

One way of telling the front-end that we need to transfer is a template like this:

```jinja2
#TRANSFER#
{
    "agent_name": "{{ a_property_for_the_target_agent_name }}",
    "model_key": "{{ a_property_for_the_target_model_key }}",
    "new_session_details": {{ actor_input }}
}
```

Here we indicate to the front-end "This is a Transfer!" and we pass the details it needs as
JSON. Here we pass the name of the new agent, the model key it needs to start using and the
details of the new session -- and this is where we plug in the JSON we received from the invoker.
The front-end can now detect a transfer, set the agent name and model key, update the session_id
and get on with the dialogue with the new agent.

### Getting back to an existing agent session
The return value of the `SendGenieEventInvoker` is whatever was rendered by sending that agent
the event and content defined in the request. The invoker will receive another `AIResponse`
from the _target_ agent, in JSON. That will contain the details required to pick up the
conversation with that target agent. It will contain the `session_id`, any `response` or maybe
some error message.

We now need to inform the front-end that it needs again to transfer. So, one way of making that
happen is by sending it something like:

```jinja2
#TRANSFER#
{
    "agent_name": "The Name of the Existing Agent",
    "model_key": "the model key of the existing agent",
    "new_session_details": {{ actor_input }}
}
```

Here, the name of the agent and their model key are stated. The `new_session_details` will
contain the `AIResponse` received as a result of the call to the `event` endpoint of the
_target_ agent. So, all the user interface will need to do is: detect this considers a transfer,
update their internal state (`session_id`, `model_key`) and get on with the conversation.

### Retaining state
Although it is tempting to retain some form of history in the front-end (what was the previous
`session_id`, what was their name, etc.) it is important to leave all that to the backend. It
is the backend that should tell the front-end what the necessary details are for the remainder
of the conversation.

### Chat History recovery
Getting back a historical chat, when there are transfers across the chat, would mean that the
front-end needs to start reading chat history from the very first agent. That chat history will
contain moments where chat is transferred to another agent. The client should then:
1. render the original chat history all the way till the transfer happens,
2. get the chat history from the session that was transferred to and render that,
3. and if the second agent transfers back, continue rendering the chat history from the 
   original agent.
4. if a third agent is called, the front-end would at that point retrieve the relevant chat
   history of that third agent

So when rendering chat history across transfers takes some more logic from the front-end.

## Invokers
### Starting a new session with the `StartGenieSessionInvoker`
The `StartGenieSessionInvoker` can work with two types of value for the rendered template. The
`content` can either be a JSON-serialization of a `StartSessionRequest` object, or any other
string.

A `StartSessionRequest` has the following properties:

`target_url`
: an optional url to override what has been configured for the invocation (either in the `meta.yaml`
  or by the accompanying environment variable).

`target_model_key`
: an optional model key to override what has been configured for the invocation.

`user_info`
: the user info that is passed to the target agent. Needs to adhere to the `User` schema. This
  user info, if provided to the originating agent, will be available in `secondary_store` under
  the key `user_info`.

`seed_data`
: the data that will be served as the `seed_data` for the new session. NB: This should be a
  string, so when a structured data object is to be passed, that object will need to be
  serialized into a string and deserialized at the receiving agent's `seed_model` method.

If the template fails to render to a JSON that can be deserialized to a `StartSessionReques`,
the values for `target_url` and `target_model_key` are used from the configuration. The value
for `user_info` will be left undefined, and the raw rendered template is used as `render_data`.

### Resuming an existing session with a `SendGenieEventInvoker`
If a dialogue is transferred to another agent, and that agent wants to transfer back to the
originating agent, their session id needs to be remembered. Using that session id, a user can
be transferred back to the originating session using the `SendGenieEventInvoker`.

The `SendGenieEventInvoker` required the `content` to contain a JSON version of a 
`SendGenieEventRequest`, containing the following properties:

`target_model_key`
: a potential model ket to override what has been configured for the invocation (either in the
`meta.yaml` or by the accompanying environment variable).

`user_info`
: the user info that is passed to the target agent. Needs to adhere to the `User` schema.

`session_id`
: the session id of an existing session at the target agent.

`event`
: the name of the event to send to the receiving agent.

`content`
: the content to send with the event to the receiving agent.

Invoking the `SendGenieEventInvoker` will send the given event, with the given content to the
receiving agent. The response from that agent (an `AIResponse` object) will be returned. This
response can be used to inform the user to transfer to another agent.