Skip to content
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
47 changes: 43 additions & 4 deletions docs/manuals/user_guide_agents_external_access.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ The external application needs:
- An agent access key.
- A role name.
- A chat history.
- A session id if the application wants progress/task state to be remembered during a run.

For the production server, the backend base URL is:

Expand Down Expand Up @@ -161,6 +162,9 @@ Body fields:
| `user_information` | array of strings | No | Extra user or game-state facts to include in the prompt. |
| `user_actions` | array of strings | No | Recent actions from the external application or game. |
| `progress` | array | No | Structured task progress entries. |
| `session_id` | string | No | External session identifier. If supplied, the backend automatically adds recent stored progress for this session. |
| `include_progress` | boolean | No | Defaults to `true`. Set to `false` to prevent automatic session progress from being added. |
| `progress_limit` | number | No | Defaults to `5`. Maximum number of recent stored progress tasks to add. Maximum accepted value is `20`. |

Example:

Expand Down Expand Up @@ -241,6 +245,7 @@ Example:
"agent_id": "6a1d614955f55909e1272f02",
"active_role_id": "instructor",
"access_key": "YOUR_AGENT_ACCESS_KEY",
"session_id": "unity-session-001",
"chat_log": [
{
"role": "user",
Expand Down Expand Up @@ -281,6 +286,8 @@ Example:

These fields are optional. Existing plain chat integrations can keep sending only `agent_id`, `active_role_id`, `access_key`, and `chat_log`.

If `session_id` is included, the backend loads the most recent stored progress tasks for that agent/session and adds them to the prompt automatically. The prompt tells the LLM to use this progress only when it is relevant to the user's question, current task, or next action.

## Voice Endpoints

RAGdoll includes Whisper-based transcription endpoints for external applications that send recorded audio.
Expand Down Expand Up @@ -364,7 +371,28 @@ Example response shape:

Progress endpoints are intended for external training applications, games, or simulators that want to store task state and reuse it in later chat requests.

Progress is stored in memory in the backend process. For durable game state, the external application should still keep its own source of truth and send relevant progress in the chat command.
Progress is stored in memory in the backend process and is scoped by:

- `agent_id`
- `session_id`

Progress entries are pruned after roughly 24 hours and also disappear if the backend restarts. For durable game state, the external application should still keep its own source of truth.

Recommended external workflow:

1. Generate a `session_id` when the external run starts.
2. Store that `session_id` in the game/client memory.
3. Send the same `session_id` with progress updates.
4. Send the same `session_id` with `/api/chat/ask` or `/api/chat/askTranscribe`.
5. The backend automatically includes the most recent progress tasks in the LLM prompt.

Example session id:

```text
unity-session-2026-06-02-player-123
```

The session id does not need to be secret. The access key is the authorization credential.

### Initialize Tasks

Expand All @@ -378,6 +406,7 @@ Body fields:
| --- | --- | --- | --- |
| `agent_id` | string | Yes | Agent id connected to the access key. |
| `access_key` | string | Yes | Raw agent access key. |
| `session_id` | string | Recommended | External session identifier. Defaults to `default` if omitted. |
| `items` | array | Yes | List of progress task objects. |

Example:
Expand All @@ -386,6 +415,7 @@ Example:
{
"agent_id": "6a1d614955f55909e1272f02",
"access_key": "YOUR_AGENT_ACCESS_KEY",
"session_id": "unity-session-001",
"items": [
{
"task_name": "Repair engine",
Expand All @@ -409,6 +439,7 @@ Body fields:
| --- | --- | --- | --- |
| `agent_id` | string | Yes | Agent id connected to the access key. |
| `access_key` | string | Yes | Raw agent access key. |
| `session_id` | string | Recommended | External session identifier. Defaults to `default` if omitted. |
| `task_name` | string | Yes | Task identifier. |
| `description` | string | Yes | Human-readable task description. |
| `status` | string | Yes | `pending`, `started`, or `complete`. |
Expand All @@ -420,6 +451,7 @@ Example:
{
"agent_id": "6a1d614955f55909e1272f02",
"access_key": "YOUR_AGENT_ACCESS_KEY",
"session_id": "unity-session-001",
"task_name": "Repair engine",
"description": "Repair the ship engine",
"status": "started",
Expand All @@ -443,7 +475,7 @@ Example:
### Fetch Progress

```http
GET /api/progress?agent_id=AGENT_ID
GET /api/progress?agent_id=AGENT_ID&session_id=SESSION_ID
```

Header:
Expand All @@ -455,11 +487,18 @@ access-key: YOUR_AGENT_ACCESS_KEY
Example:

```bash
curl "https://iplvr.it.ntnu.no/backend/api/progress?agent_id=6a1d614955f55909e1272f02" \
curl "https://iplvr.it.ntnu.no/backend/api/progress?agent_id=6a1d614955f55909e1272f02&session_id=unity-session-001" \
-H "access-key: YOUR_AGENT_ACCESS_KEY"
```

Fetched progress can be passed back into `/api/chat/ask` or `/api/chat/askTranscribe` in the optional `progress` field.
Optional query fields:

| Field | Type | Description |
| --- | --- | --- |
| `session_id` | string | Return progress for this session. Defaults to `default` if omitted. |
| `limit` | number | Return only the newest N progress tasks. |

Fetched progress can be passed back into `/api/chat/ask` or `/api/chat/askTranscribe` in the optional `progress` field, but this is no longer required when the chat request includes the same `session_id`. The backend automatically loads recent progress for that session.

## External Chat UI for Testing

Expand Down
14 changes: 14 additions & 0 deletions src/models/chat/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ class Command(BaseModel):
access_key: str | None = Field(
default=None, description="API key for agent access authorization"
)
session_id: str | None = Field(
default=None,
description="External session identifier used to load recent progress",
)
include_progress: bool = Field(
default=True,
description="If true, include recent stored session progress in the prompt",
)
progress_limit: int = Field(
default=5,
ge=0,
le=20,
description="Maximum number of recent stored progress tasks to include",
)
user_information: list[str] = Field(
default_factory=list,
description="Optional external user/game-state facts to include in the prompt",
Expand Down
3 changes: 3 additions & 0 deletions src/models/training/progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ class ProgressData(BaseModel):
status: str = Field(default="started")
agent_id: str | None = None
access_key: str | None = None
session_id: str | None = None
user_id: str | None = None
subtask_progress: list[SubtaskProgressDTO] = Field(default_factory=list)
started_at: datetime | None = None
completed_at: datetime | None = None
updated_at: datetime | None = None
completet_at: datetime | None = (
None # Note: typo in original, keeping for compatibility
)
Expand All @@ -75,4 +77,5 @@ class ListProgressData(BaseModel):

agent_id: str
access_key: str
session_id: str | None = None
items: list[ProgressData] = Field(default_factory=list)
5 changes: 4 additions & 1 deletion src/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ def assemble_prompt_with_agent(command: Command, agent: Agent) -> dict:
"response_length": len(parsed_response),
"agent_name": agent.name,
"num_context_retrieved": len(retrieved_contexts),
"num_progress_items": len(command.progress),
},
"function_call": function_call,
"response": parsed_response,
Expand Down Expand Up @@ -305,6 +306,8 @@ def game_context_prompt_section(command: Command) -> str:

return (
"Additional live context from the external application. Use it only when it is relevant "
"to the user's question and the character's role:\n"
"to the user's question, the user's current task, or the character's role. "
"Do not mention task progress unless it helps answer the user or guide their next action. Tasks may be incomplete, do not assume that it is done entirely unless status is finished, as the user may need guidance in finishing the task: :\n"
+ "\n\n".join(sections)

)
21 changes: 21 additions & 0 deletions src/routes/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)
from src.models.errors import LLMAPIError, LLMGenerationError
from src.pipeline import assemble_prompt_with_agent
from src.routes.progress import get_recent_progress_for_session
from src.transcribe import transcribe_audio, transcribe_from_upload


Expand Down Expand Up @@ -46,6 +47,24 @@ def _get_authorized_agent(command: Command):
return agent, None


def _attach_recent_progress(command: Command) -> None:
if not command.include_progress or not command.session_id or command.progress_limit <= 0:
return

recent_progress = get_recent_progress_for_session(
command.agent_id, command.session_id, command.progress_limit
)
if not recent_progress:
return

explicit_task_names = {task.task_name for task in command.progress}
merged_progress = [
task for task in recent_progress if task.task_name not in explicit_task_names
]
merged_progress.extend(command.progress)
command.progress = merged_progress[: command.progress_limit]


@router.post("/ask", response_model=Command)
async def ask(command: Command):
"""Process a user question using the specified agent and roles.
Expand Down Expand Up @@ -80,6 +99,7 @@ async def ask(command: Command):
agent, error_response = _get_authorized_agent(command)
if error_response is not None:
return error_response
_attach_recent_progress(command)

# Generate response using agent configuration and role-based RAG
response = assemble_prompt_with_agent(command, agent)
Expand Down Expand Up @@ -171,6 +191,7 @@ async def ask_transcribe(
agent, error_response = _get_authorized_agent(command)
if error_response is not None:
return error_response
_attach_recent_progress(command)

try:
response = assemble_prompt_with_agent(command, agent)
Expand Down
Loading
Loading