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
15 changes: 14 additions & 1 deletion ravendb/documents/operations/ai/open_ai_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ def __init__(
dimensions: int = None,
temperature: float = None,
embeddings_max_concurrent_batches: int = None,
reasoning_effort: str = None,
):
super().__init__(api_key, endpoint, model, dimensions, temperature, embeddings_max_concurrent_batches)
self.organization_id = organization_id
self.project_id = project_id
# The reasoning_effort sent to the provider, controlling the reasoning depth of supported models
# (such as the GPT-5 family). Typical values are "none", "minimal", "low", "medium", "high", "xhigh"
# and "max"; model families accept different subsets. Sent as supplied apart from trimming and not
# validated, so new provider values can be used directly. OpenAI accepts lowercase values only.
# When not set the field is omitted and the model applies its own default.
self.reasoning_effort = reasoning_effort

@classmethod
def from_json(cls, json_dict: Dict[str, Any]) -> "OpenAiSettings":
Expand All @@ -32,10 +39,13 @@ def from_json(cls, json_dict: Dict[str, Any]) -> "OpenAiSettings":
embeddings_max_concurrent_batches=(
json_dict["EmbeddingsMaxConcurrentBatches"] if "EmbeddingsMaxConcurrentBatches" in json_dict else None
),
reasoning_effort=(
json_dict["ReasoningEffort"] if isinstance(json_dict.get("ReasoningEffort"), str) else None
),
)

def to_json(self) -> Dict[str, Any]:
return {
json_dict = {
"ApiKey": self.api_key,
"Endpoint": self.endpoint,
"Model": self.model,
Expand All @@ -45,3 +55,6 @@ def to_json(self) -> Dict[str, Any]:
"ProjectId": self.project_id,
"EmbeddingsMaxConcurrentBatches": self.embeddings_max_concurrent_batches,
}
if self.reasoning_effort is not None and self.reasoning_effort.strip():
json_dict["ReasoningEffort"] = self.reasoning_effort
return json_dict
70 changes: 70 additions & 0 deletions ravendb/tests/operations_tests/test_open_ai_reasoning_effort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import unittest

from ravendb.documents.operations.ai.ai_connection_string import AiConnectionString, AiModelType
from ravendb.documents.operations.ai.open_ai_settings import OpenAiSettings
from ravendb.documents.operations.connection_string.get_connection_string_operation import GetConnectionStringsOperation
from ravendb.documents.operations.connection_string.put_connection_string_operation import (
PutConnectionStringOperation,
)
from ravendb.serverwide.server_operation_executor import ConnectionStringType
from ravendb.tests.test_base import TestBase

_ENDPOINT = "https://api.openai.com/"


class TestOpenAiReasoningEffortSerialization(unittest.TestCase):
def test_reasoning_effort_is_written_when_set(self):
for effort in ["high", "xhigh", "none", "High", "future-effort"]:
with self.subTest(effort=effort):
settings = OpenAiSettings("api-key", _ENDPOINT, "gpt-5-mini", reasoning_effort=effort)
self.assertEqual(effort, settings.to_json()["ReasoningEffort"])

def test_reasoning_effort_is_omitted_when_not_set_or_blank(self):
for effort in [None, "", " "]:
with self.subTest(effort=effort):
settings = OpenAiSettings("api-key", _ENDPOINT, "gpt-5-mini", reasoning_effort=effort)
self.assertNotIn("ReasoningEffort", settings.to_json())

def test_reasoning_effort_is_read_from_json(self):
json_dict = {"ApiKey": "api-key", "Endpoint": _ENDPOINT, "Model": "gpt-5-mini", "ReasoningEffort": "High"}
self.assertEqual("High", OpenAiSettings.from_json(json_dict).reasoning_effort)

def test_missing_or_numeric_reasoning_effort_reads_as_not_configured(self):
json_dict = {"ApiKey": "api-key", "Endpoint": _ENDPOINT, "Model": "gpt-5-mini"}
self.assertIsNone(OpenAiSettings.from_json(json_dict).reasoning_effort)

json_dict["ReasoningEffort"] = 3
self.assertIsNone(OpenAiSettings.from_json(json_dict).reasoning_effort)

def test_json_round_trip(self):
settings = OpenAiSettings("api-key", _ENDPOINT, "gpt-5.2", reasoning_effort="xhigh")
self.assertEqual("xhigh", OpenAiSettings.from_json(settings.to_json()).reasoning_effort)


class TestOpenAiReasoningEffortConnectionString(TestBase):
def setUp(self):
super().setUp()

def test_reasoning_effort_survives_a_connection_string_round_trip(self):
# "High" is also the shape persisted by earlier enum-based versions
for effort in ["High", "xhigh", None]:
with self.subTest(effort=effort):
connection_string = AiConnectionString(
name="reasoning-effort-round-trip",
identifier="reasoning-effort-round-trip",
model_type=AiModelType.CHAT,
openai_settings=OpenAiSettings("api-key", _ENDPOINT, "gpt-5-mini", reasoning_effort=effort),
)

self.store.maintenance.send(PutConnectionStringOperation(connection_string))

result = self.store.maintenance.send(
GetConnectionStringsOperation(connection_string.name, ConnectionStringType.AI)
)
settings = result.ai_connection_strings[connection_string.name].openai_settings

self.assertEqual(effort, settings.reasoning_effort)


if __name__ == "__main__":
unittest.main()
Loading