Skip to content

Commit da7fa43

Browse files
authored
Merge pull request #260 from poissoncorp/RDBC-967
RDBC-967 Python 7.1.3->7.1.4 Sync + GenAI Tasks Operations
2 parents 71607f3 + 8e58c9c commit da7fa43

27 files changed

+2480
-31
lines changed

ravendb/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,15 @@
8282
AiOperations,
8383
AiConversation,
8484
AiConversationResult,
85+
ContentPart,
86+
TextPart,
87+
AiMessagePromptFields,
88+
AiMessagePromptTypes,
8589
)
8690
from ravendb.documents.operations.ai.agents import (
8791
AiAgentConfiguration,
8892
AiAgentConfigurationResult,
93+
AiAgentParameter,
8994
AiAgentToolAction,
9095
AiAgentToolQuery,
9196
AiAgentPersistenceConfiguration,

ravendb/documents/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from ravendb.documents.starting_point_change_vector import StartingPointChangeVector
2+
3+
__all__ = [
4+
"StartingPointChangeVector",
5+
]

ravendb/documents/ai/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
from .ai_conversation import AiConversation
33
from .ai_conversation_result import AiConversationResult
44
from .ai_answer import AiAnswer, AiConversationStatus
5+
from .content_part import ContentPart, TextPart, AiMessagePromptFields, AiMessagePromptTypes
56

67
__all__ = [
78
"AiOperations",
89
"AiConversation",
910
"AiConversationResult",
1011
"AiAnswer",
1112
"AiConversationStatus",
13+
"ContentPart",
14+
"TextPart",
15+
"AiMessagePromptFields",
16+
"AiMessagePromptTypes",
1217
]

ravendb/documents/ai/ai_conversation.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from datetime import timedelta
77

88
from ravendb.documents.ai.ai_answer import AiAnswer, AiConversationStatus
9+
from ravendb.documents.ai.content_part import ContentPart, TextPart
910
from ravendb.documents.operations.ai.agents import (
1011
AiAgentActionRequest,
1112
AiAgentActionResponse,
@@ -53,7 +54,7 @@ def __init__(
5354
self._conversation_id = conversation_id
5455
self._change_vector = change_vector
5556

56-
self._prompt_parts: List[str] = []
57+
self._prompt_parts: List[ContentPart] = []
5758
self._action_responses: List[AiAgentActionResponse] = []
5859
self._action_requests: Optional[List[AiAgentActionRequest]] = None
5960

@@ -269,7 +270,7 @@ def set_user_prompt(self, user_prompt: str) -> None:
269270
if not user_prompt or user_prompt.isspace():
270271
raise ValueError("User prompt cannot be empty or whitespace-only")
271272
self._prompt_parts.clear()
272-
self._prompt_parts.append(user_prompt)
273+
self.add_user_prompt(user_prompt)
273274

274275
def add_user_prompt(self, *prompts: str) -> None:
275276
"""
@@ -284,7 +285,7 @@ def add_user_prompt(self, *prompts: str) -> None:
284285
for prompt in prompts:
285286
if not prompt or prompt.isspace():
286287
raise ValueError("User prompt cannot be empty or whitespace-only")
287-
self._prompt_parts.append(prompt)
288+
self._prompt_parts.append(TextPart(prompt))
288289

289290
def handle(
290291
self,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from __future__ import annotations
2+
from typing import Dict, Any
3+
4+
5+
class AiMessagePromptFields:
6+
"""Constants for AI message prompt field names."""
7+
8+
TEXT = "text"
9+
TYPE = "type"
10+
11+
12+
class AiMessagePromptTypes:
13+
"""Constants for AI message prompt types."""
14+
15+
TEXT = "text"
16+
17+
18+
class ContentPart:
19+
"""
20+
Base class for content parts in AI prompts.
21+
Content parts allow structured prompt content with different types (text, etc.).
22+
"""
23+
24+
def __init__(self, content_type: str):
25+
self._type = content_type
26+
27+
@property
28+
def type(self) -> str:
29+
return self._type
30+
31+
def to_json(self) -> Dict[str, Any]:
32+
"""
33+
Converts the content part to a JSON-serializable dictionary.
34+
Subclasses should override this method to include their specific fields.
35+
"""
36+
return {AiMessagePromptFields.TYPE: self._type}
37+
38+
39+
class TextPart(ContentPart):
40+
"""
41+
Represents a text content part in AI prompts.
42+
"""
43+
44+
def __init__(self, text: str):
45+
super().__init__(AiMessagePromptTypes.TEXT)
46+
self._text = text
47+
48+
@property
49+
def text(self) -> str:
50+
return self._text
51+
52+
@text.setter
53+
def text(self, value: str):
54+
self._text = value
55+
56+
def to_json(self) -> Dict[str, Any]:
57+
return {
58+
AiMessagePromptFields.TYPE: self._type,
59+
AiMessagePromptFields.TEXT: self._text,
60+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from ravendb.documents.operations.ai.ai_connection_string import (
2+
AiConnectionString,
3+
AiModelType,
4+
AiConnectorType,
5+
)
6+
from ravendb.documents.operations.ai.ai_task_identifier_helper import AiTaskIdentifierHelper
7+
from ravendb.documents.operations.ai.gen_ai_transformation import GenAiTransformation
8+
from ravendb.documents.operations.ai.gen_ai_configuration import GenAiConfiguration
9+
from ravendb.documents.operations.ai.abstract_ai_integration_configuration import AbstractAiIntegrationConfiguration
10+
from ravendb.documents.operations.ai.ai_task_operation_results import (
11+
AddAiTaskOperationResult,
12+
AddGenAiOperationResult,
13+
AddEmbeddingsGenerationOperationResult,
14+
)
15+
from ravendb.documents.operations.ai.add_gen_ai_operation import AddGenAiOperation
16+
from ravendb.documents.operations.ai.update_gen_ai_operation import UpdateGenAiOperation
17+
18+
__all__ = [
19+
"AiConnectionString",
20+
"AiModelType",
21+
"AiConnectorType",
22+
"AiTaskIdentifierHelper",
23+
"GenAiTransformation",
24+
"GenAiConfiguration",
25+
"AbstractAiIntegrationConfiguration",
26+
"AddAiTaskOperationResult",
27+
"AddGenAiOperationResult",
28+
"AddEmbeddingsGenerationOperationResult",
29+
"AddGenAiOperation",
30+
"UpdateGenAiOperation",
31+
]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from __future__ import annotations
2+
from abc import ABC
3+
from typing import TYPE_CHECKING, Optional, List
4+
5+
from ravendb.documents.operations.etl.configuration import EtlConfiguration
6+
from ravendb.documents.operations.ai.ai_connection_string import AiConnectionString, AiConnectorType
7+
from ravendb.documents.operations.etl.transformation import Transformation
8+
9+
if TYPE_CHECKING:
10+
pass
11+
12+
13+
class AbstractAiIntegrationConfiguration(EtlConfiguration[AiConnectionString], ABC):
14+
"""
15+
Base class for AI integration configurations.
16+
Extends EtlConfiguration with AiConnectionString as the connection type.
17+
"""
18+
19+
def __init__(
20+
self,
21+
name: Optional[str] = None,
22+
task_id: int = 0,
23+
connection_string_name: Optional[str] = None,
24+
mentor_node: Optional[str] = None,
25+
pin_to_mentor_node: bool = False,
26+
transforms: Optional[List[Transformation]] = None,
27+
disabled: bool = False,
28+
allow_etl_on_non_encrypted_channel: bool = False,
29+
):
30+
super().__init__(
31+
name=name,
32+
task_id=task_id,
33+
connection_string_name=connection_string_name,
34+
mentor_node=mentor_node,
35+
pin_to_mentor_node=pin_to_mentor_node,
36+
transforms=transforms,
37+
disabled=disabled,
38+
allow_etl_on_non_encrypted_channel=allow_etl_on_non_encrypted_channel,
39+
)
40+
41+
@property
42+
def ai_connector_type(self) -> AiConnectorType:
43+
"""Returns the AI connector type based on the active provider in the connection."""
44+
if self.connection:
45+
return self.connection.get_active_provider()
46+
return AiConnectorType.NONE
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from __future__ import annotations
2+
import json
3+
from typing import Optional, TYPE_CHECKING
4+
from urllib.parse import quote
5+
6+
from ravendb.documents.operations.definitions import MaintenanceOperation
7+
from ravendb.documents.conventions import DocumentConventions
8+
from ravendb.http.raven_command import RavenCommand
9+
from ravendb.http.server_node import ServerNode
10+
from ravendb.documents.starting_point_change_vector import StartingPointChangeVector
11+
from ravendb.documents.operations.ai.ai_task_operation_results import AddGenAiOperationResult
12+
import requests
13+
14+
from ravendb.util.util import RaftIdGenerator
15+
16+
if TYPE_CHECKING:
17+
from ravendb.documents.operations.ai.gen_ai_configuration import GenAiConfiguration
18+
19+
20+
class AddGenAiOperation(MaintenanceOperation[AddGenAiOperationResult]):
21+
"""
22+
Operation to add a new GenAI task to the database.
23+
"""
24+
25+
def __init__(
26+
self,
27+
configuration: GenAiConfiguration,
28+
starting_point: Optional[StartingPointChangeVector] = StartingPointChangeVector.LAST_DOCUMENT,
29+
):
30+
if configuration is None:
31+
raise ValueError("configuration cannot be None")
32+
33+
self._configuration = configuration
34+
self._starting_point = starting_point
35+
36+
def get_command(self, conventions: DocumentConventions) -> RavenCommand[AddGenAiOperationResult]:
37+
return AddGenAiCommand(self._configuration, self._starting_point, conventions)
38+
39+
40+
class AddGenAiCommand(RavenCommand[AddGenAiOperationResult]):
41+
def __init__(
42+
self,
43+
configuration: GenAiConfiguration,
44+
starting_point: StartingPointChangeVector,
45+
conventions: DocumentConventions,
46+
):
47+
super().__init__(AddGenAiOperationResult)
48+
self._configuration = configuration
49+
self._starting_point = starting_point
50+
self._conventions = conventions
51+
52+
def is_read_request(self) -> bool:
53+
return False
54+
55+
def create_request(self, node: ServerNode) -> requests.Request:
56+
url = f"{node.url}/databases/{node.database}/admin/etl?changeVector={quote(self._starting_point.value)}"
57+
58+
body_json = self._configuration.to_json()
59+
body = json.dumps(body_json)
60+
61+
request = requests.Request("PUT", url)
62+
request.headers = {"Content-Type": "application/json"}
63+
request.data = body
64+
return request
65+
66+
def set_response(self, response: str, from_cache: bool) -> None:
67+
if response is None:
68+
self.result = AddGenAiOperationResult()
69+
return
70+
71+
response_json = json.loads(response)
72+
self.result = AddGenAiOperationResult.from_json(response_json)
73+
74+
def get_raft_unique_request_id(self) -> str:
75+
return RaftIdGenerator.new_id()

ravendb/documents/operations/ai/agents/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .ai_agent_configuration import (
22
AiAgentConfiguration,
3+
AiAgentParameter,
34
AiAgentToolAction,
45
AiAgentToolQuery,
56
AiAgentPersistenceConfiguration,
@@ -33,6 +34,7 @@
3334
__all__ = [
3435
"AiAgentConfiguration",
3536
"AiAgentConfigurationResult",
37+
"AiAgentParameter",
3638
"AiAgentToolAction",
3739
"AiAgentToolQuery",
3840
"AiAgentPersistenceConfiguration",

0 commit comments

Comments
 (0)