99from ravendb .http .server_node import ServerNode
1010import requests
1111from ravendb .http .misc import ResponseDisposeHandling
12+ from ravendb .documents .ai .content_part import ContentPart
1213
1314
1415TSchema = TypeVar ("TSchema" )
@@ -64,6 +65,7 @@ class AiUsage:
6465 completion_tokens : int = 0
6566 total_tokens : int = 0
6667 cached_tokens : int = 0
68+ reasoning_tokens : int = 0
6769
6870 @classmethod
6971 def from_json (cls , json_dict : Dict [str , Any ]) -> AiUsage :
@@ -72,6 +74,7 @@ def from_json(cls, json_dict: Dict[str, Any]) -> AiUsage:
7274 completion_tokens = json_dict .get ("CompletionTokens" , 0 ),
7375 total_tokens = json_dict .get ("TotalTokens" , 0 ),
7476 cached_tokens = json_dict .get ("CachedTokens" , 0 ),
77+ reasoning_tokens = json_dict .get ("ReasoningTokens" , 0 ),
7578 )
7679
7780 def to_json (self ) -> Dict [str , Any ]:
@@ -80,8 +83,36 @@ def to_json(self) -> Dict[str, Any]:
8083 "CompletionTokens" : self .completion_tokens ,
8184 "TotalTokens" : self .total_tokens ,
8285 "CachedTokens" : self .cached_tokens ,
86+ "ReasoningTokens" : self .reasoning_tokens ,
8387 }
8488
89+ @staticmethod
90+ def get_usage_difference (current : AiUsage , previous : AiUsage ) -> AiUsage :
91+ """
92+ Calculate the usage difference between current and previous usage.
93+
94+ Args:
95+ current: The current usage statistics
96+ previous: The previous usage statistics
97+
98+ Returns:
99+ An AiUsage object representing the difference
100+ """
101+ previous_total_without_reasoning = (
102+ previous .completion_tokens - previous .reasoning_tokens + previous .prompt_tokens
103+ )
104+ return AiUsage (
105+ # in case the model gives us crappy results and current.prompt_tokens - previous_total_without_reasoning < 0
106+ prompt_tokens = max (current .prompt_tokens - previous_total_without_reasoning , 0 ),
107+ # in case the model gives us crappy results and current.total_tokens - previous_total_without_reasoning < 0
108+ total_tokens = max (current .total_tokens - previous_total_without_reasoning , 0 ),
109+ # we don't want to subtract cached tokens, as they are only for the last response
110+ cached_tokens = current .cached_tokens ,
111+ # we don't want to subtract completion tokens, as they are only for the last response
112+ completion_tokens = current .completion_tokens ,
113+ reasoning_tokens = current .reasoning_tokens ,
114+ )
115+
85116
86117class ConversationResult (Generic [TSchema ]):
87118 def __init__ (
@@ -161,11 +192,11 @@ class ConversationRequestBody:
161192 def __init__ (
162193 self ,
163194 action_responses : Optional [List [AiAgentActionResponse ]] = None ,
164- user_prompt : Optional [List [str ]] = None ,
195+ user_prompt : Optional [List [ContentPart ]] = None ,
165196 creation_options : Optional [AiConversationCreationOptions ] = None ,
166197 ):
167198 self .action_responses : Optional [List [AiAgentActionResponse ]] = action_responses
168- self .user_prompt : Optional [List [str ]] = user_prompt # List of prompt parts
199+ self .user_prompt : Optional [List [ContentPart ]] = user_prompt # List of ContentPart objects
169200 self .creation_options : Optional [AiConversationCreationOptions ] = creation_options
170201
171202 def to_json (self ) -> Dict [str , Any ]:
@@ -182,8 +213,10 @@ def to_json(self) -> Dict[str, Any]:
182213 None if self .action_responses is None else [resp .to_json () for resp in self .action_responses ]
183214 )
184215
185- # UserPrompt: null if None, otherwise array (even if empty)
186- result ["UserPrompt" ] = self .user_prompt
216+ # UserPrompt: null if None, otherwise array of ContentPart JSON objects
217+ result ["UserPrompt" ] = (
218+ None if self .user_prompt is None else [part .to_json () for part in self .user_prompt ]
219+ )
187220
188221 # CreationOptions: always present (create empty if None, matching C# behavior)
189222 result ["CreationOptions" ] = (self .creation_options or AiConversationCreationOptions ()).to_json ()
@@ -203,7 +236,7 @@ def __init__(
203236 self ,
204237 agent_id : str ,
205238 conversation_id : str ,
206- prompt_parts : Optional [List [str ]] = None ,
239+ prompt_parts : Optional [List [ContentPart ]] = None ,
207240 action_responses : Optional [List [AiAgentActionResponse ]] = None ,
208241 options : Optional [AiConversationCreationOptions ] = None ,
209242 change_vector : Optional [str ] = None ,
@@ -216,7 +249,7 @@ def __init__(
216249 Args:
217250 agent_id: The ID of the AI agent (required)
218251 conversation_id: The ID of the conversation (required)
219- prompt_parts: List of prompt strings to send to the agent
252+ prompt_parts: List of ContentPart objects to send to the agent
220253 action_responses: List of action responses from previous turn
221254 options: Creation options including parameters and expiration
222255 change_vector: Change vector for optimistic concurrency
@@ -258,7 +291,7 @@ def __init__(
258291 self ,
259292 agent_id : str ,
260293 conversation_id : str ,
261- prompt_parts : Optional [List [str ]] = None ,
294+ prompt_parts : Optional [List [ContentPart ]] = None ,
262295 action_responses : Optional [List [AiAgentActionResponse ]] = None ,
263296 options : Optional [AiConversationCreationOptions ] = None ,
264297 change_vector : Optional [str ] = None ,
@@ -309,7 +342,7 @@ def create_request(self, node: ServerNode) -> requests.Request:
309342 # Build request body with correct structure to match .NET client
310343 request_body = ConversationRequestBody (
311344 action_responses = self ._action_responses ,
312- user_prompt = "" . join ( self ._prompt_parts ) ,
345+ user_prompt = self ._prompt_parts ,
313346 creation_options = self ._options ,
314347 )
315348
0 commit comments