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
5 changes: 5 additions & 0 deletions ravendb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,15 @@
AiOperations,
AiConversation,
AiConversationResult,
ContentPart,
TextPart,
AiMessagePromptFields,
AiMessagePromptTypes,
)
from ravendb.documents.operations.ai.agents import (
AiAgentConfiguration,
AiAgentConfigurationResult,
AiAgentParameter,
AiAgentToolAction,
AiAgentToolQuery,
AiAgentPersistenceConfiguration,
Expand Down
5 changes: 5 additions & 0 deletions ravendb/documents/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from ravendb.documents.starting_point_change_vector import StartingPointChangeVector

__all__ = [
"StartingPointChangeVector",
]
5 changes: 5 additions & 0 deletions ravendb/documents/ai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
from .ai_conversation import AiConversation
from .ai_conversation_result import AiConversationResult
from .ai_answer import AiAnswer, AiConversationStatus
from .content_part import ContentPart, TextPart, AiMessagePromptFields, AiMessagePromptTypes

__all__ = [
"AiOperations",
"AiConversation",
"AiConversationResult",
"AiAnswer",
"AiConversationStatus",
"ContentPart",
"TextPart",
"AiMessagePromptFields",
"AiMessagePromptTypes",
]
7 changes: 4 additions & 3 deletions ravendb/documents/ai/ai_conversation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from datetime import timedelta

from ravendb.documents.ai.ai_answer import AiAnswer, AiConversationStatus
from ravendb.documents.ai.content_part import ContentPart, TextPart
from ravendb.documents.operations.ai.agents import (
AiAgentActionRequest,
AiAgentActionResponse,
Expand Down Expand Up @@ -53,7 +54,7 @@ def __init__(
self._conversation_id = conversation_id
self._change_vector = change_vector

self._prompt_parts: List[str] = []
self._prompt_parts: List[ContentPart] = []
self._action_responses: List[AiAgentActionResponse] = []
self._action_requests: Optional[List[AiAgentActionRequest]] = None

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

def add_user_prompt(self, *prompts: str) -> None:
"""
Expand All @@ -284,7 +285,7 @@ def add_user_prompt(self, *prompts: str) -> None:
for prompt in prompts:
if not prompt or prompt.isspace():
raise ValueError("User prompt cannot be empty or whitespace-only")
self._prompt_parts.append(prompt)
self._prompt_parts.append(TextPart(prompt))

def handle(
self,
Expand Down
60 changes: 60 additions & 0 deletions ravendb/documents/ai/content_part.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from __future__ import annotations
from typing import Dict, Any


class AiMessagePromptFields:
"""Constants for AI message prompt field names."""

TEXT = "text"
TYPE = "type"


class AiMessagePromptTypes:
"""Constants for AI message prompt types."""

TEXT = "text"


class ContentPart:
"""
Base class for content parts in AI prompts.
Content parts allow structured prompt content with different types (text, etc.).
"""

def __init__(self, content_type: str):
self._type = content_type

@property
def type(self) -> str:
return self._type

def to_json(self) -> Dict[str, Any]:
"""
Converts the content part to a JSON-serializable dictionary.
Subclasses should override this method to include their specific fields.
"""
return {AiMessagePromptFields.TYPE: self._type}


class TextPart(ContentPart):
"""
Represents a text content part in AI prompts.
"""

def __init__(self, text: str):
super().__init__(AiMessagePromptTypes.TEXT)
self._text = text

@property
def text(self) -> str:
return self._text

@text.setter
def text(self, value: str):
self._text = value

def to_json(self) -> Dict[str, Any]:
return {
AiMessagePromptFields.TYPE: self._type,
AiMessagePromptFields.TEXT: self._text,
}
31 changes: 31 additions & 0 deletions ravendb/documents/operations/ai/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from ravendb.documents.operations.ai.ai_connection_string import (
AiConnectionString,
AiModelType,
AiConnectorType,
)
from ravendb.documents.operations.ai.ai_task_identifier_helper import AiTaskIdentifierHelper
from ravendb.documents.operations.ai.gen_ai_transformation import GenAiTransformation
from ravendb.documents.operations.ai.gen_ai_configuration import GenAiConfiguration
from ravendb.documents.operations.ai.abstract_ai_integration_configuration import AbstractAiIntegrationConfiguration
from ravendb.documents.operations.ai.ai_task_operation_results import (
AddAiTaskOperationResult,
AddGenAiOperationResult,
AddEmbeddingsGenerationOperationResult,
)
from ravendb.documents.operations.ai.add_gen_ai_operation import AddGenAiOperation
from ravendb.documents.operations.ai.update_gen_ai_operation import UpdateGenAiOperation

__all__ = [
"AiConnectionString",
"AiModelType",
"AiConnectorType",
"AiTaskIdentifierHelper",
"GenAiTransformation",
"GenAiConfiguration",
"AbstractAiIntegrationConfiguration",
"AddAiTaskOperationResult",
"AddGenAiOperationResult",
"AddEmbeddingsGenerationOperationResult",
"AddGenAiOperation",
"UpdateGenAiOperation",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from __future__ import annotations
from abc import ABC
from typing import TYPE_CHECKING, Optional, List

from ravendb.documents.operations.etl.configuration import EtlConfiguration
from ravendb.documents.operations.ai.ai_connection_string import AiConnectionString, AiConnectorType
from ravendb.documents.operations.etl.transformation import Transformation

if TYPE_CHECKING:
pass


class AbstractAiIntegrationConfiguration(EtlConfiguration[AiConnectionString], ABC):
"""
Base class for AI integration configurations.
Extends EtlConfiguration with AiConnectionString as the connection type.
"""

def __init__(
self,
name: Optional[str] = None,
task_id: int = 0,
connection_string_name: Optional[str] = None,
mentor_node: Optional[str] = None,
pin_to_mentor_node: bool = False,
transforms: Optional[List[Transformation]] = None,
disabled: bool = False,
allow_etl_on_non_encrypted_channel: bool = False,
):
super().__init__(
name=name,
task_id=task_id,
connection_string_name=connection_string_name,
mentor_node=mentor_node,
pin_to_mentor_node=pin_to_mentor_node,
transforms=transforms,
disabled=disabled,
allow_etl_on_non_encrypted_channel=allow_etl_on_non_encrypted_channel,
)

@property
def ai_connector_type(self) -> AiConnectorType:
"""Returns the AI connector type based on the active provider in the connection."""
if self.connection:
return self.connection.get_active_provider()
return AiConnectorType.NONE
75 changes: 75 additions & 0 deletions ravendb/documents/operations/ai/add_gen_ai_operation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from __future__ import annotations
import json
from typing import Optional, TYPE_CHECKING
from urllib.parse import quote

from ravendb.documents.operations.definitions import MaintenanceOperation
from ravendb.documents.conventions import DocumentConventions
from ravendb.http.raven_command import RavenCommand
from ravendb.http.server_node import ServerNode
from ravendb.documents.starting_point_change_vector import StartingPointChangeVector
from ravendb.documents.operations.ai.ai_task_operation_results import AddGenAiOperationResult
import requests

from ravendb.util.util import RaftIdGenerator

if TYPE_CHECKING:
from ravendb.documents.operations.ai.gen_ai_configuration import GenAiConfiguration


class AddGenAiOperation(MaintenanceOperation[AddGenAiOperationResult]):
"""
Operation to add a new GenAI task to the database.
"""

def __init__(
self,
configuration: GenAiConfiguration,
starting_point: Optional[StartingPointChangeVector] = StartingPointChangeVector.LAST_DOCUMENT,
):
if configuration is None:
raise ValueError("configuration cannot be None")

self._configuration = configuration
self._starting_point = starting_point

def get_command(self, conventions: DocumentConventions) -> RavenCommand[AddGenAiOperationResult]:
return AddGenAiCommand(self._configuration, self._starting_point, conventions)


class AddGenAiCommand(RavenCommand[AddGenAiOperationResult]):
def __init__(
self,
configuration: GenAiConfiguration,
starting_point: StartingPointChangeVector,
conventions: DocumentConventions,
):
super().__init__(AddGenAiOperationResult)
self._configuration = configuration
self._starting_point = starting_point
self._conventions = conventions

def is_read_request(self) -> bool:
return False

def create_request(self, node: ServerNode) -> requests.Request:
url = f"{node.url}/databases/{node.database}/admin/etl?changeVector={quote(self._starting_point.value)}"

body_json = self._configuration.to_json()
body = json.dumps(body_json)

request = requests.Request("PUT", url)
request.headers = {"Content-Type": "application/json"}
request.data = body
return request

def set_response(self, response: str, from_cache: bool) -> None:
if response is None:
self.result = AddGenAiOperationResult()
return

response_json = json.loads(response)
self.result = AddGenAiOperationResult.from_json(response_json)

def get_raft_unique_request_id(self) -> str:
return RaftIdGenerator.new_id()
2 changes: 2 additions & 0 deletions ravendb/documents/operations/ai/agents/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .ai_agent_configuration import (
AiAgentConfiguration,
AiAgentParameter,
AiAgentToolAction,
AiAgentToolQuery,
AiAgentPersistenceConfiguration,
Expand Down Expand Up @@ -33,6 +34,7 @@
__all__ = [
"AiAgentConfiguration",
"AiAgentConfigurationResult",
"AiAgentParameter",
"AiAgentToolAction",
"AiAgentToolQuery",
"AiAgentPersistenceConfiguration",
Expand Down
Loading