Skip to content
This repository was archived by the owner on Mar 4, 2026. It is now read-only.

Commit 096a18d

Browse files
authored
Merge pull request #10 from UiPath/fix/add_chat_models
fix: add chat streaming models
2 parents 79a7da6 + 63cbd9e commit 096a18d

12 files changed

Lines changed: 653 additions & 2 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-core"
3-
version = "0.0.5"
3+
version = "0.0.6"
44
description = "UiPath Core abstractions"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath/core/chat/__init__.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
"""UiPath Conversation Models.
2+
3+
This module provides Pydantic models that represent the JSON event schema for conversations between a client (UI) and an LLM/agent.
4+
5+
The event objects define a hierarchical conversation structure:
6+
7+
* Conversation
8+
* Exchange
9+
* Message
10+
* Content Parts
11+
* Citations
12+
* Tool Calls
13+
* Tool Results
14+
15+
A conversation may contain multiple exchanges, and an exchange may contain multiple messages. A message may contain
16+
multiple content parts, each of which can be text or binary, including media input and output streams; and each
17+
content part can include multiple citations. A message may also contain multiple tool calls, which may contain a tool
18+
result.
19+
20+
The protocol also supports a top level, "async", input media streams (audio and video), which can span multiple
21+
exchanges. These are used for Gemini's automatic turn detection mode, where the LLM determines when the user has
22+
stopped talking and starts producing output. The output forms one or more messages in an exchange with no explicit
23+
input message. However, the LLM may produce an input transcript which can be used to construct the implicit input
24+
message that started the exchange.
25+
26+
In addition, the protocol also supports "async" tool calls that span multiple exchanges. This can be used with
27+
Gemini's asynchronous function calling protocol, which allows function calls to produce results that interrupt the
28+
conversation when ready, even after multiple exchanges. They also support generating multiple results from a single
29+
tool call. By contrast most tool calls are scoped to a single message, which contains both the call and the single
30+
result produced by that call.
31+
32+
Not all features supported by the protocol will be supported by all clients and LLMs. The optional top level
33+
`capabilities` property can be used to communicate information about supported features. This property should be set
34+
on the first event written to a new websocket connection. This initial event may or may not contain additional
35+
sub-events.
36+
"""
37+
38+
from .async_stream import (
39+
UiPathConversationAsyncInputStreamEndEvent,
40+
UiPathConversationAsyncInputStreamEvent,
41+
UiPathConversationAsyncInputStreamStartEvent,
42+
UiPathConversationInputStreamChunkEvent,
43+
)
44+
from .citation import (
45+
UiPathConversationCitation,
46+
UiPathConversationCitationEndEvent,
47+
UiPathConversationCitationEvent,
48+
UiPathConversationCitationSource,
49+
UiPathConversationCitationSourceMedia,
50+
UiPathConversationCitationSourceUrl,
51+
UiPathConversationCitationStartEvent,
52+
)
53+
from .content import (
54+
InlineOrExternal,
55+
UiPathConversationContentPart,
56+
UiPathConversationContentPartChunkEvent,
57+
UiPathConversationContentPartEndEvent,
58+
UiPathConversationContentPartEvent,
59+
UiPathConversationContentPartStartEvent,
60+
UiPathExternalValue,
61+
UiPathInlineValue,
62+
)
63+
from .conversation import (
64+
UiPathConversationCapabilities,
65+
UiPathConversationEndEvent,
66+
UiPathConversationStartedEvent,
67+
UiPathConversationStartEvent,
68+
)
69+
from .event import UiPathConversationEvent
70+
from .exchange import (
71+
UiPathConversationExchange,
72+
UiPathConversationExchangeEndEvent,
73+
UiPathConversationExchangeEvent,
74+
UiPathConversationExchangeStartEvent,
75+
)
76+
from .message import (
77+
UiPathConversationMessage,
78+
UiPathConversationMessageEndEvent,
79+
UiPathConversationMessageEvent,
80+
UiPathConversationMessageStartEvent,
81+
)
82+
from .meta import UiPathConversationMetaEvent
83+
from .tool import (
84+
UiPathConversationToolCall,
85+
UiPathConversationToolCallEndEvent,
86+
UiPathConversationToolCallEvent,
87+
UiPathConversationToolCallResult,
88+
UiPathConversationToolCallStartEvent,
89+
)
90+
91+
__all__ = [
92+
# Root
93+
"UiPathConversationEvent",
94+
# Conversation
95+
"UiPathConversationCapabilities",
96+
"UiPathConversationStartEvent",
97+
"UiPathConversationStartedEvent",
98+
"UiPathConversationEndEvent",
99+
# Exchange
100+
"UiPathConversationExchangeStartEvent",
101+
"UiPathConversationExchangeEndEvent",
102+
"UiPathConversationExchangeEvent",
103+
"UiPathConversationExchange",
104+
# Message
105+
"UiPathConversationMessageStartEvent",
106+
"UiPathConversationMessageEndEvent",
107+
"UiPathConversationMessageEvent",
108+
"UiPathConversationMessage",
109+
# Content
110+
"UiPathConversationContentPartChunkEvent",
111+
"UiPathConversationContentPartStartEvent",
112+
"UiPathConversationContentPartEndEvent",
113+
"UiPathConversationContentPartEvent",
114+
"UiPathConversationContentPart",
115+
"UiPathInlineValue",
116+
"UiPathExternalValue",
117+
"InlineOrExternal",
118+
# Citation
119+
"UiPathConversationCitationStartEvent",
120+
"UiPathConversationCitationEndEvent",
121+
"UiPathConversationCitationEvent",
122+
"UiPathConversationCitationSource",
123+
"UiPathConversationCitationSourceUrl",
124+
"UiPathConversationCitationSourceMedia",
125+
"UiPathConversationCitation",
126+
# Tool
127+
"UiPathConversationToolCallStartEvent",
128+
"UiPathConversationToolCallEndEvent",
129+
"UiPathConversationToolCallEvent",
130+
"UiPathConversationToolCallResult",
131+
"UiPathConversationToolCall",
132+
# Async Stream
133+
"UiPathConversationInputStreamChunkEvent",
134+
"UiPathConversationAsyncInputStreamStartEvent",
135+
"UiPathConversationAsyncInputStreamEndEvent",
136+
"UiPathConversationAsyncInputStreamEvent",
137+
# Meta
138+
"UiPathConversationMetaEvent",
139+
]
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""Async input stream events."""
2+
3+
from typing import Any
4+
5+
from pydantic import BaseModel, ConfigDict, Field
6+
7+
8+
class UiPathConversationInputStreamChunkEvent(BaseModel):
9+
"""Represents a single chunk of input stream data."""
10+
11+
input_stream_sequence: int | None = Field(None, alias="inputStreamSequence")
12+
data: str
13+
14+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
15+
16+
17+
class UiPathConversationAsyncInputStreamStartEvent(BaseModel):
18+
"""Signals the start of an asynchronous input stream."""
19+
20+
mime_type: str = Field(..., alias="mimeType")
21+
start_of_speech_sensitivity: str | None = Field(
22+
None, alias="startOfSpeechSensitivity"
23+
)
24+
end_of_speech_sensitivity: str | None = Field(None, alias="endOfSpeechSensitivity")
25+
prefix_padding_ms: int | None = Field(None, alias="prefixPaddingMs")
26+
silence_duration_ms: int | None = Field(None, alias="silenceDurationMs")
27+
metadata: dict[str, Any] | None = Field(None, alias="metaData")
28+
29+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
30+
31+
32+
class UiPathConversationAsyncInputStreamEndEvent(BaseModel):
33+
"""Signals the end of an asynchronous input stream."""
34+
35+
metadata: dict[str, Any] | None = Field(None, alias="metaData")
36+
last_chunk_content_part_sequence: int | None = Field(
37+
None, alias="lastChunkContentPartSequence"
38+
)
39+
40+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
41+
42+
43+
class UiPathConversationAsyncInputStreamEvent(BaseModel):
44+
"""Encapsulates sub-events related to an asynchronous input stream."""
45+
46+
stream_id: str = Field(..., alias="streamId")
47+
start: UiPathConversationAsyncInputStreamStartEvent | None = None
48+
end: UiPathConversationAsyncInputStreamEndEvent | None = None
49+
chunk: UiPathConversationInputStreamChunkEvent | None = None
50+
meta_event: dict[str, Any] | None = Field(None, alias="metaEvent")
51+
52+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)

src/uipath/core/chat/citation.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""Citation events for message content."""
2+
3+
from typing import Any
4+
5+
from pydantic import BaseModel, ConfigDict, Field
6+
7+
8+
class UiPathConversationCitationStartEvent(BaseModel):
9+
"""Indicates the start of a citation target in a content part."""
10+
11+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
12+
13+
14+
class UiPathConversationCitationEndEvent(BaseModel):
15+
"""Indicates the end of a citation target in a content part."""
16+
17+
sources: list[dict[str, Any]]
18+
19+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
20+
21+
22+
class UiPathConversationCitationEvent(BaseModel):
23+
"""Encapsulates sub-events related to citations."""
24+
25+
citation_id: str = Field(..., alias="citationId")
26+
start: UiPathConversationCitationStartEvent | None = None
27+
end: UiPathConversationCitationEndEvent | None = None
28+
29+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
30+
31+
32+
class UiPathConversationCitationSourceUrl(BaseModel):
33+
"""Represents a citation source that can be rendered as a link (URL)."""
34+
35+
url: str
36+
37+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
38+
39+
40+
class UiPathConversationCitationSourceMedia(BaseModel):
41+
"""Represents a citation source that references media, such as a PDF document."""
42+
43+
mime_type: str = Field(..., alias="mimeType")
44+
download_url: str | None = Field(None, alias="downloadUrl")
45+
page_number: str | None = Field(None, alias="pageNumber")
46+
47+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
48+
49+
50+
class UiPathConversationCitationSource(BaseModel):
51+
"""Represents a citation source, either a URL or media reference."""
52+
53+
title: str | None = None
54+
55+
# Union of Url or Media
56+
url: str | None = None
57+
mime_type: str | None = Field(None, alias="mimeType")
58+
download_url: str | None = Field(None, alias="downloadUrl")
59+
page_number: str | None = Field(None, alias="pageNumber")
60+
61+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
62+
63+
64+
class UiPathConversationCitation(BaseModel):
65+
"""Represents a citation or reference inside a content part."""
66+
67+
citation_id: str = Field(..., alias="citationId")
68+
offset: int
69+
length: int
70+
sources: list[UiPathConversationCitationSource]
71+
72+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)

src/uipath/core/chat/content.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""Message content part events."""
2+
3+
from typing import Any
4+
5+
from pydantic import BaseModel, ConfigDict, Field
6+
7+
from .citation import UiPathConversationCitation, UiPathConversationCitationEvent
8+
9+
10+
class UiPathConversationContentPartChunkEvent(BaseModel):
11+
"""Contains a chunk of a message content part."""
12+
13+
content_part_sequence: int | None = Field(None, alias="contentPartSequence")
14+
data: str | None = None
15+
citation: UiPathConversationCitationEvent | None = None
16+
17+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
18+
19+
20+
class UiPathConversationContentPartStartEvent(BaseModel):
21+
"""Signals the start of a message content part."""
22+
23+
mime_type: str = Field(..., alias="mimeType")
24+
metadata: dict[str, Any] | None = Field(None, alias="metaData")
25+
26+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
27+
28+
29+
class UiPathConversationContentPartEndEvent(BaseModel):
30+
"""Signals the end of a message content part."""
31+
32+
last_chunk_content_part_sequence: int | None = Field(
33+
None, alias="lastChunkContentPartSequence"
34+
)
35+
interrupted: dict[str, Any] | None = None
36+
metadata: dict[str, Any] | None = Field(None, alias="metaData")
37+
38+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
39+
40+
41+
class UiPathConversationContentPartEvent(BaseModel):
42+
"""Encapsulates events related to message content parts."""
43+
44+
content_part_id: str = Field(..., alias="contentPartId")
45+
start: UiPathConversationContentPartStartEvent | None = None
46+
end: UiPathConversationContentPartEndEvent | None = None
47+
chunk: UiPathConversationContentPartChunkEvent | None = None
48+
meta_event: dict[str, Any] | None = Field(None, alias="metaEvent")
49+
50+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
51+
52+
53+
class UiPathInlineValue(BaseModel):
54+
"""Used when a value is small enough to be returned inline."""
55+
56+
inline: Any
57+
58+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
59+
60+
61+
class UiPathExternalValue(BaseModel):
62+
"""Used when a value is too large to be returned inline."""
63+
64+
url: str
65+
byte_count: int | None = Field(None, alias="byteCount")
66+
67+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
68+
69+
70+
InlineOrExternal = UiPathInlineValue | UiPathExternalValue
71+
72+
73+
class UiPathConversationContentPart(BaseModel):
74+
"""Represents a single part of message content."""
75+
76+
content_part_id: str = Field(..., alias="contentPartId")
77+
mime_type: str = Field(..., alias="mimeType")
78+
data: InlineOrExternal
79+
citations: list[UiPathConversationCitation] | None = None
80+
is_transcript: bool | None = Field(None, alias="isTranscript")
81+
is_incomplete: bool | None = Field(None, alias="isIncomplete")
82+
83+
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)

0 commit comments

Comments
 (0)