Skip to content

Releases: ravendb/ravendb-python-client

7.2.7 - Concurrency Checks, Subscription Status & AI Text Answers 🐍

Choose a tag to compare

@poissoncorp poissoncorp released this 23 Oct 02:23
5134ed2

7.2.7 syncs the Python client with the RavenDB 7.2.7 C# client. A session can now register a concurrency check for a document it never loaded. Subscription workers report their state and stay connected while idle, instead of reconnecting every few seconds. AI conversations can override the output schema for one turn and return plain text.

Highlights

Register for concurrency check

session.advanced.register_for_concurrency_check(key, change_vector) makes the next save_changes verify that a document still has the given change vector, without loading it and without a remote call. It works in every optimistic_concurrency_mode. An empty string asserts that the document does not exist, and None removes the check. A cluster-wide session raises ValueError.

from ravendb import DocumentStore
from ravendb.exceptions.raven_exceptions import ConcurrencyException

store = DocumentStore(urls=["http://localhost:8080"], database="Shop")
store.initialize()

with store.open_session() as session:
    # the order was read elsewhere; save only if nobody changed it since
    session.advanced.register_for_concurrency_check("orders/1-A", known_change_vector)
    session.store(invoice, "invoices/1-A")
    try:
        session.save_changes()
    except ConcurrencyException:
        print("orders/1-A changed, invoice not saved")

New API: session.advanced.register_for_concurrency_check

Subscription worker status

SubscriptionWorker.status tells what the worker is doing (CONNECTING, WAITING_FOR_DOCUMENTS, PROCESSING, RETRYING, FAULTED, STOPPED), with the failure that caused a retry and since when it fails. Handlers can follow every change and every established connection. An idle worker now stays connected in WAITING_FOR_DOCUMENTS, as in C#; the new connection_stream_timeout (default 30 s, minimum 15 s) still detects a connection that died silently.

from datetime import timedelta
from ravendb import SubscriptionWorkerOptions, SubscriptionWorkerState

options = SubscriptionWorkerOptions("orders-feed", connection_stream_timeout=timedelta(seconds=45))
worker = store.subscriptions.get_subscription_worker(options)

def on_state_changed(sender, status):
    if status.state == SubscriptionWorkerState.RETRYING:
        print(f"retrying since {status.failing_since_utc}: {status.exception}")

worker.add_on_state_changed(on_state_changed)
worker.add_on_established_subscription_connection(lambda: print("connected"))
worker.run(lambda batch: print(f"{len(batch.items)} orders"))

New classes: SubscriptionWorkerState, SubscriptionWorkerStatus
New options: SubscriptionWorkerOptions.connection_stream_timeout

AI conversations: per-turn schema and plain text

run_with_schema and stream_with_schema take an AiOutputOptions, a JSON schema string or a sample object, so one turn can ask for a different shape than the agent's own schema. run_text() and stream_text(on_chunk) return the model's answer as plain text. Tools without parameters are registered with handle_no_args() and receive_no_args().

chat = store.ai.conversation(agent_id, "chats/")

chat.set_user_prompt("Which supplier ships fastest?")
answer = chat.run_with_schema({"Supplier": "", "Days": 0})
print(answer.answer)

chat.set_user_prompt("Summarize that in one sentence")
print(chat.run_text().answer)

chat.set_user_prompt("And for Germany?")
chat.stream_text(lambda chunk: print(chunk, end=""))

New methods: AiConversation.run_text, AiConversation.stream_text, AiConversation.handle_no_args, AiConversation.receive_no_args

Other Changes

  • IndexCompilationException for indexes that fail to compile; create_index_definition raises it instead of RuntimeError.
  • UnsupportedAiQueryParameterException and AiAgentParameterCollisionException, mapped from the server's errors and exported from ravendb.
  • OpenAiSettings.reasoning_effort, for models such as GPT-5.6 that reject tools without it.
  • created_at on the messages of GetConversationMessagesOperation.
  • A server error that arrives inside a streamed AI conversation is raised instead of ending the stream silently.
  • generate_identifier() rejects names of server-wide AI connection strings with no ASCII letter or digit.
  • Subscriptions with counter or time series includes send a single include clause the server accepts.
  • A subscription connection closed by the server raises ConnectionError and reconnects, instead of spinning on the read; the receive buffer is cleared on every new connection.

7.2.6.post1 - NoLeaderException typing πŸ”§

Choose a tag to compare

@poissoncorp poissoncorp released this 17 Sep 10:10
0169ddd

A single fix on top of 7.2.6.

PyPI: https://pypi.org/project/ravendb/7.2.6.post1/

NoLeaderException is typed again

The server sends NoLeaderException. The exception map was keyed on NoLoaderException, a name the server never sends, so a real no-leader failure reached callers as a plain RavenException and could not be caught by type.

try:
    store.maintenance.server.send(DeleteDatabaseOperation("orders", hard_delete=True))
except NoLeaderException:
    pass

The class is renamed to match. NoLoaderException stays as an alias, so existing imports and handlers keep working.

7.2.6 - JsonPatch, Queue Sink, CDC Sink & Smuggler πŸπŸ”€

Choose a tag to compare

@poissoncorp poissoncorp released this 16 Sep 13:35
90f7b56

7.2.6 syncs the Python client with the RavenDB 7.2.5 and 7.2.6 C# clients in one go, and it is a big one. The session patch methods now build RFC 6902 JsonPatch commands instead of JavaScript patch scripts. Three subsystems the Python client simply never had arrive complete: Queue Sink, CDC Sink and the database smuggler. Server-wide connection strings, SSO on client certificates and per-turn output schemas for AI agents land alongside them. And 269 types that existed but were only reachable through their module paths are now importable straight from ravendb.

Highlights

Session patching with JsonPatch

session.advanced.patch, patch_array and patch_object now emit an RFC 6902 JsonPatch command wherever the path and the value allow it, so the server applies the change structurally instead of running a patch script. Anything JsonPatch cannot express keeps the old JavaScript path automatically: composite values, remove_all predicates, increment, and paths that are not addressable as a JSON pointer. Patches for the same document merge into a single command, as they always did.

from ravendb import DocumentStore

store = DocumentStore(urls=["http://localhost:8080"], database="NorthWind")
store.initialize()

with store.open_session() as session:
    session.advanced.patch("users/1-A", "Name", "patched")      # add   /Name
    session.advanced.patch("users/1-A", "Address.City", "Krakow")  # add   /Address/City
    session.advanced.patch("users/1-A", "Tags[0]", "primary")   # replace /Tags/0
    session.advanced.patch_array("users/1-A", "Tags", lambda tags: tags.add("new"))
    session.save_changes()  # one JsonPatch command, four operations

The standalone operation is available too, for building a patch by hand:

from ravendb import JsonPatchDocument, JsonPatchOperation

patch = JsonPatchDocument().add("/Name", "patched").add("/Tags/-", "c").replace("/Tags/0", "z")
result = store.operations.send(JsonPatchOperation("users/1-A", patch))

Set store.conventions.session_patch_behavior = SessionPatchBehavior.JAVA_SCRIPT before initialize() to stay on the previous code path entirely.

New operations: JsonPatchOperation
New classes: JsonPatchDocument, JsonPatchResult, JsonPatchCommandData
New enums: SessionPatchBehavior (JSON_PATCH default, JAVA_SCRIPT), CommandType.JSON_PATCH
New convention: DocumentConventions.session_patch_behavior

Queue Sink

RavenDB consuming messages from Kafka, RabbitMQ, Azure Queue Storage, Amazon SQS or Azure Service Bus. The client had the QueueSink ongoing-task enum member and nothing behind it: no configuration, no operations, no task result. It now has all of it, including DatabaseRecord.queue_sinks.

from ravendb import (
    AddQueueSinkOperation, AzureServiceBusSinkSource,
    QueueBrokerType, QueueSinkConfiguration, QueueSinkScript,
)

store.maintenance.send(AddQueueSinkOperation(QueueSinkConfiguration(
    name="orders-sink",
    broker_type=QueueBrokerType.AZURE_SERVICE_BUS,
    connection_string_name="asb",
    scripts=[QueueSinkScript("orders", [
        AzureServiceBusSinkSource.queue("orders"),
        AzureServiceBusSinkSource.subscription("events", "audit"),
    ], "put('orders/', this);")],
)))

New operations: AddQueueSinkOperation, UpdateQueueSinkOperation
New classes: QueueSinkConfiguration, QueueSinkScript, AzureServiceBusSinkSource, QueueSinkProcessState, OngoingTaskQueueSink, AddQueueSinkOperationResult, UpdateQueueSinkOperationResult

CDC Sink

The whole public configuration tree (13 model types, from CdcColumnMapping up to CdcSinkConfiguration, with embedded and linked tables nesting to any depth), plus the two task operations, the ongoing-task result with its lag and health fields, and DatabaseRecord.cdc_sinks.

from ravendb import (
    AddCdcSinkOperation, CdcColumnMapping, CdcColumnType, CdcSinkConfiguration,
    CdcSinkOnDeleteConfig, CdcSinkPostgresSettings, CdcSinkTableConfig,
)

store.maintenance.send(AddCdcSinkOperation(CdcSinkConfiguration(
    name="orders-cdc", connection_string_name="pg",
    postgres=CdcSinkPostgresSettings(publication_name="pub", slot_name="slot"),
    tables=[CdcSinkTableConfig(
        collection_name="Orders", source_table_name="orders",
        columns=[CdcColumnMapping("payload", "Payload", CdcColumnType.JSON)],
        primary_key_columns=["id"],
        on_delete=CdcSinkOnDeleteConfig(ignore_deletes=True))],
)))

Schema discovery and mapping preview come with it: GetCdcSinkSchemaOperation browses the source database a CDC task would read: tables, columns, primary and foreign keys, each annotated with what CDC can actually capture. TestCdcSinkMappingOperation runs real rows through the configured mapping without saving anything.

New operations: AddCdcSinkOperation, UpdateCdcSinkOperation, GetCdcSinkSchemaOperation, TestCdcSinkMappingOperation
New classes: CdcSinkConfiguration, CdcSinkTableConfig, CdcSinkEmbeddedTableConfig, CdcSinkLinkedTableConfig, CdcSinkOnDeleteConfig, CdcSinkPostgresSettings, CdcColumnMapping, OngoingTaskCdcSink
New enums: CdcColumnType, CdcSinkRelationType, CdcSinkProcessState, CdcSinkTaskState, CdcSinkTableLoadState, OngoingTaskType.CDC_SINK

Database smuggler

store.smuggler exports a database to a file or a stream and imports one back. Item selection is a set of DatabaseItemType members, rendered to the comma-separated string the server parses.

from ravendb import DatabaseItemType, DatabaseSmugglerExportOptions, DatabaseSmugglerImportOptions

store.smuggler.export(DatabaseSmugglerExportOptions(
    operate_on_types={DatabaseItemType.DOCUMENTS},
    collections=["Orders"]), "dump.ravendbdump").wait_for_completion()

other.smuggler.import_data(DatabaseSmugglerImportOptions(), "dump.ravendbdump")
store.smuggler.for_database("reporting").import_incremental(options, backup_dir)

Results are typed. A finished smuggler operation hands back a SmugglerResult with counts per item type, rather than a raw dictionary:

result = store.smuggler.import_data(options, "dump.ravendbdump").wait_for_completion()

result.documents.read_count
result.documents.attachments.read_count
result.database_record.updated

New property: store.smuggler
New classes: DatabaseSmuggler, DatabaseSmugglerExportOptions, DatabaseSmugglerImportOptions, DatabaseSmugglerOptions, SmugglerResult, SmugglerProgressBase, DatabaseRecordProgress, SmugglerOperation
New enums: DatabaseItemType, DatabaseRecordItemType, ExportCompressionAlgorithm

Server-wide connection strings

A connection string the cluster propagates to every database except the ones you exclude. ServerWideConnectionString flattens the underlying connection string into the request body, the shape the endpoint expects, and dispatches back to the right concrete type on read.

from ravendb import RavenConnectionString
from ravendb.serverwide.operations.connection_strings import (
    PutServerWideConnectionStringOperation, ServerWideConnectionString,
)

store.maintenance.server.send(PutServerWideConnectionStringOperation(
    ServerWideConnectionString(
        connection_string=RavenConnectionString("shared", "orders", ["http://localhost:8080"]),
        excluded_databases=["scratch"])))

# the name a propagated connection string carries inside a database record
ServerWideConnectionString.get_database_record_connection_string_name("shared")
# -> "Server Wide Connection String, shared"

New operations: PutServerWideConnectionStringOperation, GetServerWideConnectionStringsOperation, RemoveServerWideConnectionStringOperation
New classes: ServerWideConnectionString, ConnectionStringUsage
New enum: ConnectionStringUsageKind

AI agents: per-turn output schema

An agent's output schema is fixed when the agent is created. A single conversation turn can now override it without disturbing the turns around it.

from ravendb import AiOutputOptions

chat = store.ai.conversation("agents/support", "conversations/")
chat.set_user_prompt("How does the customer feel about the delay?")

# a schema derived from a sample object, for this turn only
chat.run_with_schema(AiOutputOptions(sample_object={"Sentiment": "positive"}))

# an explicit JSON schema, or no structure at all so the model answers in prose
chat.run_with_schema(AiOutputOptions(output_schema=schema))
chat.run_with_schema(AiOutputOptions(no_schema=True))

C# splits these across a dozen generic overloads keyed on TAnswer; Python has one options object and two entry points. Asking for no schema while also supplying one is refused.

New classes: AiOutputOptions
New methods: AiConversation.run_with_schema, AiConversation.stream_with_schema

AI agents: reading a conversation back

store.ai.get_conversation_messages(...) takes either a conversation id or a GetConversationMessagesOptions for timestamp paging and a ...

Read more

7.2.3.post2 - Bulk Insert Performance & others

Choose a tag to compare

@poissoncorp poissoncorp released this 26 Aug 14:53
fbc98d1

Bulk insert got a performance pass: entities are converted straight to their JSON-able form without a serialize/parse round trip, finished buffers are handed to the sender instead of being copied, and the client now applies backpressure instead of buffering without a bound when the server reads slower than the client writes. BulkInsertOptions.use_compression is honored again and gzips the request body. Time series timestamps written through bulk insert are no longer shifted by the machine's local UTC offset. The client also gained an on-disk topology cache (a port of .NET's TopologyLocalCache), enabled by default, and the logs configuration API was rewritten for the RavenDB 7.x logging schema.

Docs: Bulk insert, Conventions, Time series - append

PyPi: https://pypi.org/project/ravendb/7.2.3.post2/

Highlights

Bulk insert performance

Writing a document no longer goes through json.dumps followed by json.loads - the entity is converted directly into its JSON-able form. A full buffer is handed over to the thread sending the request rather than deep-copied, and flushing is decided by the buffer size alone. The queue of buffers waiting to be sent is now bounded (BulkInsertOperation.MAX_BUFFERS_TO_FLUSH), so a server that reads slower than the client writes slows the client down instead of growing its memory. BulkInsertOptions.use_compression was previously ignored; it now gzips the outgoing body.

from ravendb.documents.bulk_insert_operation import BulkInsertOptions

options = BulkInsertOptions(use_compression=True)

with store.bulk_insert(options=options) as bulk_insert:
    for i in range(100_000):
        bulk_insert.store(User(name=f"user/{i}"))

If the request carrying the data ends before all of it was sent, the client raises instead of waiting forever for a free slot:

from ravendb.exceptions.documents.bulkinsert import BulkInsertAbortedException

try:
    with store.bulk_insert() as bulk_insert:
        bulk_insert.store(User(name="users/1"))
except BulkInsertAbortedException as e:
    print(f"Bulk insert aborted: {e}")

New constants: BulkInsertOperation.MAX_BUFFERS_TO_FLUSH
New methods: Utils.entity_to_dict reimplemented without a JSON round trip

On-disk topology cache

The client now persists the database and cluster topology to disk on every topology update, and seeds itself from that cache when none of the initial urls can be reached at startup. This mirrors .NET's TopologyLocalCache and is enabled by default, writing to a per-user OS cache directory (%LOCALAPPDATA%\ravendb\topology, ~/Library/Caches/ravendb/topology, or $XDG_CACHE_HOME/ravendb/topology). Caching is best-effort: a failure to read or write the cache never breaks a topology update.

from ravendb import DocumentStore

store = DocumentStore(["http://localhost:8080"], "Northwind")
store.conventions.topology_cache_location = "/var/cache/my-app/ravendb"
# or opt out entirely
store.conventions.disable_topology_cache = True
store.initialize()

New module: ravendb.http.topology_local_cache (try_save, try_load, server_hash)
New conventions: DocumentConventions.disable_topology_cache, DocumentConventions.topology_cache_location
New methods: Topology.to_json / Topology.from_json, ServerNode.to_json / ServerNode.from_json

Logs configuration for the RavenDB 7.x logging schema

ravendb/serverwide/operations/logs.py was rewritten against the 7.x /admin/logs/configuration endpoint. The old LogMode enum is gone, replaced by LogLevel plus filter support, and GetLogsConfigurationResult now exposes the four configuration sections the server returns (logs, audit_logs, microsoft_logs, admin_logs). SetLogsConfigurationOperation takes one configuration object plus persist, matching the .NET client, where a single call sets exactly one of Logs / MicrosoftLogs / AdminLogs.

from ravendb.serverwide.operations.logs import (
    GetLogsConfigurationOperation,
    SetLogsConfigurationOperation,
    LogLevel,
    LogFilter,
    LogFilterAction,
)

result = store.maintenance.server.send(GetLogsConfigurationOperation())
print(result.logs.current_min_level, result.logs.path)

configuration = SetLogsConfigurationOperation.LogsConfiguration(
    min_level=LogLevel.INFO,
    filters=[LogFilter(LogLevel.TRACE, LogLevel.DEBUG, "Raven.Server.Documents", LogFilterAction.IGNORE)],
    log_filter_default_action=LogFilterAction.NEUTRAL,
)
store.maintenance.server.send(SetLogsConfigurationOperation(configuration, persist=True))

New enums: LogLevel, LogFilterAction (replacing LogMode)
New classes: LogFilter, LogsConfiguration, AuditLogsConfiguration, MicrosoftLogsConfiguration, AdminLogsConfiguration, SetLogsConfigurationOperation.LogsConfiguration, SetLogsConfigurationOperation.MicrosoftLogsConfiguration, SetLogsConfigurationOperation.AdminLogsConfiguration

Breaking: code using LogMode or building SetLogsConfigurationOperation.Parameters directly needs updating to LogLevel and the new configuration objects.

Other Changes

  • Bulk insert time series: Utils.get_unix_time_in_ms treats a naive datetime as UTC, consistent with Utils.datetime_to_string. Timestamps appended through bulk insert were previously shifted by the machine's local UTC offset (RavenDB-26793).
  • TimeSeriesCollectionConfiguration.to_json no longer raises when policies or raw_policy is unset.
  • DatabaseChanges secured websocket connections use ssl.PROTOCOL_TLS_CLIENT instead of the deprecated ssl.PROTOCOL_TLSv1_2, keeping the previous verification behavior; a configured trust store still enables CA verification.
  • Replaced deprecated datetime.utcnow() calls in the request executor, the subscription worker and schema validation, and fixed an invalid escape sequence in the ijson lexeme regex - the test run is now warning-free.
  • Around 30 previously skipped tests were unskipped or fixed: Corax-limited tests now force the Lucene engine, licensed tests are gated on RAVENDB_LICENSE, and the exception-dispatcher tests expect RavenException.

Revisions Operations & Pull Replication πŸ”πŸ—‚οΈ

Choose a tag to compare

@poissoncorp poissoncorp released this 25 Jun 04:21
c3f3ca5

7.2.3.post1 is a hotfix release on top of 7.2.3 that additionally brings two client-API areas in line with the RavenDB 7.2 C# client: Revisions Operations and Pull Replication.

Docs: Document Revisions

Highlights

Revisions Operations

Maintenance operations to enforce the revisions configuration on existing data, delete and revert revisions, adopt orphaned revisions, configure the revisions-bin cleaner, and (server-wide) configure revisions for conflicting documents. Long-running operations are sent with operations.send_async(...) and awaited.

from ravendb.documents.operations.revisions import (
    DeleteRevisionsOperation,
    EnforceRevisionsConfigurationOperation,
)

# Apply the current revisions configuration to all existing revisions (long-running)
operation = store.operations.send_async(EnforceRevisionsConfigurationOperation())
operation.wait_for_completion()

# Delete every revision of a document
result = store.maintenance.send(DeleteRevisionsOperation(document_id="users/1"))
print(result.total_deletes)

New operations: EnforceRevisionsConfigurationOperation, AdoptOrphanedRevisionsOperation, DeleteRevisionsOperation, RevertRevisionsByIdOperation, ConfigureRevisionsBinCleanerOperation, ConfigureRevisionsForConflictsOperation
New classes: RevisionsBinConfiguration, RevisionsOperationParameters, RevisionsOperationContinuationParameters

Pull Replication

Define pull-replication hubs and sinks, grant and revoke certificate-based hub access, read hub task info, and update external-replication tasks.

from ravendb import PullReplicationDefinition, PutPullReplicationAsHubOperation

definition = PullReplicationDefinition("hub-1")
result = store.maintenance.send(PutPullReplicationAsHubOperation(definition))
print(result.task_id)

New operations: PutPullReplicationAsHubOperation, UpdatePullReplicationAsSinkOperation, RegisterReplicationHubAccessOperation, UnregisterReplicationHubAccessOperation, GetReplicationHubAccessOperation, GetPullReplicationTasksInfoOperation, UpdateExternalReplicationOperation
New classes: PullReplicationDefinition, PullReplicationAsSink, ReplicationHubAccess, ReplicationHubAccessResult, PullReplicationDefinitionAndCurrentConnections, OngoingTaskPullReplicationAsSink, OngoingTaskPullReplicationAsHub, PullReplicationMode, PreventDeletionsMode, ReplicationType
New exception: ReplicationHubNotFoundException

Other Changes

  • Replaced the deprecated ssl.PROTOCOL_TLSv1_2 with PROTOCOL_TLS_CLIENT in the TCP client (behavior preserved via explicit certificate pinning).
  • Migrated off the deprecated CmpXchg.value to RavenDocumentQuery.cmp_xchg().
  • Quieted noisy background-thread logging during tests.

7.2.3 - NULLS Ordering, Conversation Debugging & Embedding Context 🐍

Choose a tag to compare

@poissoncorp poissoncorp released this 19 Jun 08:59
1ab9583

7.2.3 syncs the Python client with the RavenDB 7.2.3 C# client. Three headline features land in this release: per-query NULLS ordering for controlling where null values fall in an ORDER BY, an AI conversation debug flag that records per-turn diagnostics server-side, and a context prefix for embeddings chunking that injects shared document context into every chunk.

Highlights

Per-query NULLS ordering

Every ordering method now takes an optional nulls argument (NullsOrdering.DEFAULT / FIRST / LAST) that controls where documents with a null sort value land, independently of the sort direction. Explicit FIRST / LAST placement is a Corax-only capability β€” running it against a Lucene index is rejected by the server.

from ravendb import DocumentStore, NullsOrdering, OrderingType

store = DocumentStore(urls=["http://localhost:8080"], database="NorthWind")
store.initialize()

with store.open_session() as session:
    # Documents with a null "age" are placed last, regardless of asc/desc
    results = list(
        session.advanced.document_query(object_type=User)
        .order_by("age", OrderingType.LONG, NullsOrdering.LAST)
    )

New enum: NullsOrdering (DEFAULT, FIRST, LAST)
New nulls parameter on: order_by, order_by_descending, add_order, order_by_distance, order_by_distance_descending, order_by_distance_wkt, order_by_distance_descending_wkt

AI agent conversation debugging

Pass debug=True when opening a conversation to have the server record per-turn debugging information to the @conversations-debug collection β€” useful for inspecting prompts, tool calls, and model responses after the fact. The flag is threaded through RunConversationOperation as a &debug= query-string flag.

from ravendb import DocumentStore

store = DocumentStore(urls=["http://localhost:8080"], database="NorthWind")
store.initialize()

chat = store.ai.conversation("agents/support", "conversations/", debug=True)
chat.set_user_prompt("Summarize my open orders")
answer = chat.run()

New debug parameter on store.ai.conversation(...) (wired through RunConversationOperation)

Embeddings chunking β€” context prefix

ChunkingOptions now accepts a context_prefix that is prepended to every produced chunk before it is embedded β€” handy for injecting broader document context (a title, product name, or section heading) into chunks that would otherwise be embedded in isolation. The prefix's tokens count against max_tokens_per_chunk, reducing the effective chunking budget accordingly; empty or whitespace-only prefixes are rejected at validation.

from ravendb.documents.operations.ai.chunking_options import ChunkingOptions, ChunkingMethod

options = ChunkingOptions(
    ChunkingMethod.PLAIN_TEXT_SPLIT_PARAGRAPHS,
    max_tokens_per_chunk=512,
    context_prefix="Product manual:",
)

New field: ChunkingOptions.context_prefix (JSON key: ContextPrefix)

Other Changes

  • Patch on dictionary entries now emits bracket notation with JSON-escaped keys (this.map["key.with.dots"] = ...) instead of dot access, fixing session.advanced.patch_object(...), JavaScriptMap.put, and JavaScriptMap.remove for keys containing dots, spaces, quotes, or other special characters. JavaScriptMap.remove is now also fluent (returns self).

7.2.2 - Optimistic Reads, MultiAgents & AI Attachments πŸ€–

Choose a tag to compare

@poissoncorp poissoncorp released this 29 May 11:55
9ea3601

7.2.2 syncs the Python client with the RavenDB 7.2.2 C# client. Three headline features land in this release: OptimisticConcurrencyMode.WritesAndReads for catching read-then-write races at save_changes(), AI conversation attachments for sending file bytes (or copying existing attachments) to AI agents, and AI agent sub-agents for hierarchical agent orchestration.

Highlights

OptimisticConcurrencyMode.WritesAndReads

The session-level use_optimistic_concurrency boolean is now a tri-state OptimisticConcurrencyMode enum (NONE / WRITES / WRITES_AND_READS). The new WRITES_AND_READS mode sends change vectors for every tracked entity (not just the modified ones), so save_changes() is rejected when any document the session read has changed underneath it. Both SessionOptions and DocumentConventions accept the new mode; the legacy use_optimistic_concurrency = True/False setter still works and maps to WRITES/NONE. Combining WRITES_AND_READS with no_tracking or cluster-wide transactions raises at construction time.

from ravendb import DocumentStore, OptimisticConcurrencyMode, SessionOptions
from ravendb.exceptions.raven_exceptions import ConcurrencyException

store = DocumentStore(urls=["http://localhost:8080"], database="NorthWind")
store.initialize()

opts = SessionOptions(optimistic_concurrency_mode=OptimisticConcurrencyMode.WRITES_AND_READS)
with store.open_session(session_options=opts) as session:
    order = session.load("orders/1-A")          # tracked for the read set
    customer = session.load("customers/1-A")    # also tracked

    customer.name = "ACME"                       # only customer is modified...

    try:
        session.save_changes()
    except ConcurrencyException:
        # ...but if ANYTHING in the read set (incl. orders/1-A) changed
        # on the server since we loaded it, the entire batch is rejected.
        ...

New classes: OptimisticConcurrencyMode, BatchTrackChangesCommandData
New CommandType.BATCH_TRACK_CHANGES enum value
New SessionOptions(optimistic_concurrency_mode=...) and DocumentConventions.optimistic_concurrency_mode properties
New session.advanced.optimistic_concurrency_mode getter/setter; back-compat shim preserved on use_optimistic_concurrency

AI Conversation Attachments

AiConversation now ships two new methods to attach files into an agent turn: add_attachment(name, stream, content_type) for raw bytes / binary file-likes, and copy_attachment_from(source_document_id, file_name) for re-using an attachment already stored on a document. The conversation switches to a multipart POST whose positional layout matches the server's AbstractAiAgentProcessor.ParseMultipartAsync reader: body, commands, then one part per stream tagged with Command-Type: AttachmentStream. Each stream is single-use per turn; re-using one raises immediately.

import io
from ravendb import DocumentStore

store = DocumentStore(urls=["http://localhost:8080"], database="NorthWind")
store.initialize()

with store.ai.conversation("invoice-helper", "conversations/") as conv:
    # 1. New bytes
    conv.add_attachment("march-2026.pdf", io.BytesIO(pdf_bytes), "application/pdf")
    # 2. An attachment that already lives on a document
    conv.copy_attachment_from("orders/1-A", "purchase-order.pdf")

    conv.set_user_prompt("Compare these two invoices and flag discrepancies.")
    answer = conv.run()
    print(answer.answer)

New methods: AiConversation.add_attachment(), AiConversation.copy_attachment_from()
New RunConversationOperation(attachments_commands=...) parameter and ConversationRequestBody.attachment_commands field
New Headers.COMMAND_TYPE, Headers.ATTACHMENT_STREAM constants

AI Agent Sub-Agents

AiAgentConfiguration now accepts a sub_agents list of AiAgentToolSubAgent entries (each one a (identifier, description) pair pointing at another agent the parent can dispatch to). When the model picks a sub-agent, the server reports it as an AiAgentActionRequest with type=AiAgentActionRequestType.SUB_AGENT and a sub_conversation_id linking the child conversation. Useful for splitting a single user-facing assistant into specialized workers (a benefits agent, an attendance agent, etc.) without the parent prompt growing unboundedly.

from ravendb import (
    AiAgentConfiguration,
    AiAgentToolSubAgent,
    AiAgentActionRequestType,
)

parent = AiAgentConfiguration(
    name="HRDispatcher",
    identifier="hr-dispatcher",
    connection_string_name="openai-cs",
    system_prompt="Route the user's question to the right specialist.",
    sub_agents=[
        AiAgentToolSubAgent(identifier="benefits-agent", description="Health, dental, 401k"),
        AiAgentToolSubAgent(identifier="attendance-agent", description="PTO, sick days, schedules"),
    ],
)
store.ai.add_or_update_agent(parent)

with store.ai.conversation("hr-dispatcher", "conversations/") as conv:
    conv.set_user_prompt("How many PTO days do I have left?")
    answer = conv.run()
    for action in conv.required_actions:
        if action.type == AiAgentActionRequestType.SUB_AGENT:
            print(f"Dispatched to {action.name} (conversation: {action.sub_conversation_id})")

New classes: AiAgentToolSubAgent, AiAgentActionRequestType
New AiAgentConfiguration.sub_agents field
New AiAgentActionRequest.type and AiAgentActionRequest.sub_conversation_id fields

Other Changes

  • query.with_tag(name): tag a query end-to-end (DocumentQuery, RawDocumentQuery, session.advanced.lazily, session.advanced.stream, DocumentQueryCustomization). The tag is appended as &tag=... on /queries, /streams/queries, and the multi-get query string, and surfaces in server-side query logs.
  • RavenDocumentQuery.now() / today() / cmp_xchg(): server-side method helpers for where_* comparisons. where_greater_than("expires_at", RavenDocumentQuery.now()) emits expires_at > now() instead of binding a client-side timestamp. CmpXchg.value() is preserved with a DeprecationWarning pointing at RavenDocumentQuery.cmp_xchg().
  • AiAgentParameter policy + type: new policy=AiAgentParameterPolicy.FORBID_MODEL_GENERATION flag blocks a parent agent from generating values for a sub-agent's parameter; new type=AiAgentParameterValueType.STRING/NUMBER/BOOLEAN/ARRAY_OF_*/NULL enables server-side value-type validation.
  • AiConversationCreationOptions typed parameters: AiConversationParameter(value=..., send_to_model=False) and AiConversationParameterOptions(send_to_model=False) give per-parameter control over what the model sees. Existing opts.add_parameter("name", value) and AiConversationCreationOptions(parameters={"a": 1}) calls keep working via a back-compat shim.
  • max_model_iterations_per_call: new cap on AiConversationCreationOptions to bound how many tool-loop iterations the agent may run for a single user turn.
  • CertificateMetadata.disabled: new boolean field, round-trips through CertificateDefinition, GetCertificatesOperation, and EditClientCertificateOperation.
  • Disposed-session and disposed-store guards: session.save_changes() and RequestExecutor.execute_command() now raise immediately when invoked on a disposed session or store. Set RAVEN_DISABLE_DISPOSE_CHECKS=true to suppress the cross-component check in legacy teardown paths.
  • Topology command validation: GetDatabaseTopologyCommand and GetClusterTopologyCommand now reject HTTP 200 responses whose body isn't a topology shape, with a clear "may indicate the URL does not point to a RavenDB server" error instead of returning empty results.
  • AzureOpenAiSettings endpoint validation: constructor now rejects endpoint=None or whitespace alongside the existing deployment_name guard.
  • AiMessagePromptFields.IMAGE: new field-name constant alongside TEXT / TYPE.
  • MissingAiAgentParameterException: now registered in _EXCEPTION_MAP, so server-side missing-parameter errors surface as the specific exception type instead of generic RavenException.

Fixes

  • Spatial order_by_distance() on dynamic fields: the field name was being wrapped in single quotes in the generated RQL ('@spatial(...)' > 0), producing a string literal instead of a field reference. The quotes are gone now, so order_by_distance(DynamicSpatialField, ...) actually orders by distance. Affects order_by_distance, order_by_distance_wkt, order_by_distance_descending, and order_by_distance_descending_wkt.

What's Changed

Full Changelog: 7.2.1...7.2.2

7.2.1 🐍

Choose a tag to compare

@poissoncorp poissoncorp released this 16 Apr 11:38
889b715

This release adds aggressive caching with automatic invalidation, session streaming APIs, new session introspection methods, store-level lifecycle events, and a batch of query and session correctness fixes.

Highlights

Aggressive Caching

Aggressive caching is now available in the Python client. When enabled, read requests are served from a local HTTP cache without contacting the server, as long as the cached entry is within the specified duration. In the default TRACK_CHANGES mode, the store subscribes to the Changes API and automatically invalidates stale entries when documents or indexes change on the server. Use DO_NOT_TRACK_CHANGES mode for maximum throughput when you can tolerate slightly stale reads. Both methods accept an optional database parameter to scope caching to a specific database.

Use aggressively_cache_for() to enable aggressive caching for a scope - this can wrap anything from a single operation to your entire application loop. Use disable_aggressive_caching() to temporarily bypass the cache for operations that require guaranteed-fresh data from the server.

from ravendb import DocumentStore
from ravendb.http.misc import AggressiveCacheMode
from datetime import timedelta

store = DocumentStore(urls=["http://localhost:8080"], database="NorthWind")
store.initialize()

# Enable aggressive caching with automatic invalidation (default)
with store.aggressively_cache_for(timedelta(minutes=5)):
    with store.open_session() as session:
        order = session.load("orders/1-A")  # first call hits the server
    with store.open_session() as session:
        order = session.load("orders/1-A")  # served from cache

    # Bypass cache for a critical read that must be fresh
    with store.disable_aggressive_caching():
        with store.open_session() as session:
            balance = session.load("accounts/1-A")  # always hits the server

    # Back to cached reads after exiting the disable scope
    with store.open_session() as session:
        product = session.load("products/1-A")  # served from cache

# Scope to a specific database, no change tracking
with store.aggressively_cache_for(
    timedelta(hours=1), database="Archive", mode=AggressiveCacheMode.DO_NOT_TRACK_CHANGES
):
    pass

New methods: DocumentStore.aggressively_cache_for(), DocumentStore.disable_aggressive_caching()
New classes: AggressiveCacheMode, AggressiveCacheOptions

Store-Level Lifecycle Events

Register event handlers at the DocumentStore level so they fire for every session opened from that store. This is the Python equivalent of the C# IDocumentStore.OnBeforeStore / OnAfterSaveChanges / OnBeforeDelete / OnBeforeQuery events. Each handler can be removed with the corresponding remove_* method.

from ravendb import DocumentStore

store = DocumentStore(urls=["http://localhost:8080"], database="NorthWind")
store.initialize()

def on_before_store(args):
    print(f"Storing entity with key: {args.key}")

store.add_before_store(on_before_store)

with store.open_session() as session:
    session.store(entity, "orders/1-A")
    session.save_changes()

store.remove_before_store(on_before_store)

New methods: store.add_before_store(), store.add_after_save_changes(), store.add_before_delete(), store.add_before_query() and their remove_* counterparts.

Session Streaming APIs

Stream query results or loaded documents directly to a binary output without deserializing into entities. Use query.to_stream() on any DocumentQuery or RawDocumentQuery, or session.advanced.load_into_stream() / session.advanced.stream_into() for lower-level control.

import io
from ravendb import DocumentStore

store = DocumentStore(urls=["http://localhost:8080"], database="NorthWind")
store.initialize()

with store.open_session() as session:
    buf = io.BytesIO()
    session.query(object_type=dict).to_stream(buf)
    raw_json = buf.getvalue()  # bytes containing {"Results": [...]}

New methods: DocumentQuery.to_stream(), RawDocumentQuery.to_stream(), session.advanced.stream_into(), session.advanced.load_into_stream()

Session Introspection: what_changed_for and get_tracked_entities

Inspect changes for a single entity or retrieve all tracked entities in the current session.

with store.open_session() as session:
    user = session.load("users/1-A")
    user.name = "New Name"

    changes = session.advanced.what_changed_for(user)
    # [DocumentsChanges(field_name="name", ...)]

    tracked = session.advanced.get_tracked_entities()
    # {"users/1-A": {"entity": <User>, "is_new": False, ...}}

New methods: session.advanced.what_changed_for(), session.advanced.get_tracked_entities()

Other Changes

  • skip() without take() no longer forces 0 results - queries using only .skip() now return the remaining documents as expected (RDBC-1026).
  • and_also(wrap_previous_query_clauses=True) - new parameter wraps preceding query clauses in parentheses for correct boolean precedence (RDBC-1027).
  • SuggestToken alias quoting - suggestion aliases containing spaces are now properly quoted in generated RQL (RDBC-1028).
  • JavaScriptArray.remove_all(predicate_js) - removes array items matching a JavaScript predicate via patch operations (RDBC-1040).
  • session.advanced.refresh(entity) now updates the entity in-place instead of returning a new instance (RDBC-1024).
  • lazily.load(None) now returns Lazy(None) instead of None, preventing AttributeError on chained .value calls (RDBC-1025).
  • No-tracking session includes - using .include() in a no_tracking session now raises InvalidOperationException with a clear message instead of silently failing (RDBC-1021).
  • Metadata alias isolation - DocumentInfo.get_new_document_info no longer shares a metadata dict reference that could cause cross-entity pollution (RDBC-1018).
  • Event handler recursion fix - BeforeDeleteEventArgs.session and BeforeQueryEventArgs.session no longer trigger infinite recursion (RDBC-1020).
  • Value comparison type coercion - fixed type-coercion in dirty-checking for arrays and scalar fields so has_changes() returns correct results (RDBC-1019).
  • SaveChanges correctness - fixed batch results being dropped after time series operations, cluster transaction index not propagating, and wait_for_replication/wait_for_indexes settings being silently ignored (RDBC-1048).
  • Cluster transaction validation - fixed compare exchange state checks and batch patch status handling (RDBC-1048).
  • GenAI configuration - added Version field to GenAiConfiguration for hash-version backward compatibility; prevented duplicate action responses for the same tool-id in AiConversation (RDBC-1041).
  • embedding_generations added to database record (RDBC-1056).

7.2.0 πŸ¦β€β¬›

Choose a tag to compare

@poissoncorp poissoncorp released this 06 Mar 11:10
0cfd03a

7.2.0 syncs the Python client with RavenDB 7.2 C# client. The two headline features are Remote Attachments (offloading attachment bytes to S3 or Azure Blob Storage) and JSON Schema Validation (enforcing document structure).

Docs:

Technical Guides:

Highlights

Remote Attachments

Attachments can now be pushed to a remote cloud storage automatically thanks to RavenDB new feature - Remote Attachments.
Configure one or more named destinations (S3 or Azure Blob), then schedule attachments for upload.
The server's background sender picks them up and pushes the bytes to the cloud at or after the scheduled time.

from ravendb.documents.operations.attachments import (
    ConfigureRemoteAttachmentsOperation,
    RemoteAttachmentsConfiguration,
    RemoteAttachmentsDestinationConfiguration,
    RemoteAttachmentsS3Settings,
    RemoteAttachmentParameters,
    StoreAttachmentParameters,
)
import datetime

# 1. Configure a destination (once per database)
store.maintenance.send(ConfigureRemoteAttachmentsOperation(
    RemoteAttachmentsConfiguration(
        destinations={
            "s3-archive": RemoteAttachmentsDestinationConfiguration(
                s3_settings=RemoteAttachmentsS3Settings(
                    aws_access_key="AKIAIOSFODNN7EXAMPLE",
                    aws_secret_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
                    aws_region_name="us-east-1",
                    bucket_name="my-ravendb-attachments",
                )
            )
        }
    )
))

# 2. Attach a file scheduled for cloud upload
at = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=5)
with store.open_session() as session:
    params = StoreAttachmentParameters("invoice.pdf", pdf_bytes, content_type="application/pdf")
    params.remote_parameters = RemoteAttachmentParameters(identifier="s3-archive", at=at)
    session.advanced.attachments.store_with_parameters("orders/1", params)
    session.save_changes()

Bulk insert remote attachments support

import datetime
from ravendb.documents.operations.attachments import RemoteAttachmentParameters, StoreAttachmentParameters

at = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=5)

with store.bulk_insert() as bulk_insert:
    for i in range(1000):
        doc_id = f"orders/{i}"
        bulk_insert.store_as({"name": f"Order {i}"}, doc_id)

        attachments = bulk_insert.attachments_for(doc_id)

        params = StoreAttachmentParameters("invoice.pdf", pdf_bytes, content_type="application/pdf")
        params.remote_parameters = RemoteAttachmentParameters(identifier="s3-archive", at=at)
        attachments.store_with_parameters(params)

New operations: ConfigureRemoteAttachmentsOperation, GetRemoteAttachmentsConfigurationOperation
New classes: RemoteAttachmentsConfiguration, RemoteAttachmentsS3Settings, RemoteAttachmentsAzureSettings, RemoteAttachmentsDestinationConfiguration, RemoteAttachmentParameters, StoreAttachmentParameters, RemoteAttachmentFlags
Updated: PutAttachmentOperation, GetAttachmentOperation, session store_with_parameters, bulk insert attachment support

Bulk-Delete Attachments Operation

Delete multiple attachments in a single HTTP request:

from ravendb.documents.operations.attachments import AttachmentRequest, DeleteAttachmentsOperation

store.operations.send(DeleteAttachmentsOperation([
    AttachmentRequest("orders/1", "invoice.pdf"),
    AttachmentRequest("orders/2", "photo.jpg"),
]))

JSON Schema Validation

Enforce a JSON Schema on a collection at write time. Invalid documents are rejected with SchemaValidationException. Existing documents can be audited without blocking writes using StartSchemaValidationOperation.

import json
from ravendb.documents.operations.schema_validation import (
    ConfigureSchemaValidationOperation,
    SchemaDefinition,
    SchemaValidationConfiguration,
    StartSchemaValidationOperation,
    ValidateSchemaResult,
)
from ravendb.exceptions.raven_exceptions import SchemaValidationException

schema = json.dumps({
    "type": "object",
    "properties": {"name": {"type": "string"}, "age": {"type": "integer"}},
    "required": ["name", "age"],
})

# Enforce schema on writes
store.maintenance.send(ConfigureSchemaValidationOperation(
    SchemaValidationConfiguration(
        validators_per_collection={"Users": SchemaDefinition(schema=schema)}
    )
))

# Invalid document is rejected immediately at save_changes()
try:
    with store.open_session() as session:
        session.store({"age": "not-a-number"}, "users/bad")  # missing name, wrong age type
        session.save_changes()
except SchemaValidationException as e:
    print("Rejected:", e)

# Audit existing documents (non-blocking)
params = StartSchemaValidationOperation.Parameters(schema_definition=schema, collection="Users")
op = store.maintenance.send_async(StartSchemaValidationOperation(params))
op.wait_for_completion()
result = op.fetch_operations_status()["Result"]
print(f"Scanned: {result['ValidatedCount']}, Errors: {result['ErrorCount']}")

New module: ravendb.documents.operations.schema_validation
New exception: SchemaValidationException

Schema validation inside an index definition

A schema can also be embedded directly in an IndexDefinition via schema_definitions. During indexing the map calls Schema.GetErrorsFor(doc) to project per-document validation errors into a stored field β€” without blocking writes.

import json
from ravendb.documents.indexes.definitions import FieldStorage, IndexDefinition, IndexFieldOptions
from ravendb.documents.operations.indexes import PutIndexesOperation

schema = json.dumps({
    "properties": {"age": {"minimum": 0, "maximum": 150}},
    "required": ["name", "age"],
})

index_def = IndexDefinition(
    name="Users/WithSchemaErrors",
    maps={"""
        from doc in docs.Users
        select new { doc.name, Errors = Schema.GetErrorsFor(doc) }
    """},
    fields={"Errors": IndexFieldOptions(storage=FieldStorage.YES)},
    schema_definitions={"Users": schema},  # collection β†’ JSON Schema string
)
store.maintenance.send(PutIndexesOperation(index_def))

# Query and inspect per-document errors
with store.open_session() as session:
    results = list(
        session.query_index("Users/WithSchemaErrors", dict)
               .select_fields(dict, "name", "Errors")
    )
    for r in results:
        if r.get("Errors"):
            print(r["name"], "β†’", r["Errors"])

Other Changes

  • IndexDefinition: new schema_definitions field (Dict[str, str]) linking index fields to schema names
  • DetailedDatabaseStatistics: new count_of_remote_attachments field
  • DatabaseRecord: new schema_validation and remote_attachments fields
  • Exception dispatcher: now properly wired for all non-2xx and conflict responses

PyPi link: https://pypi.org/project/ravendb/7.2.0/

PRs

Full Changelog: 7.1.5...7.2.0

7.1.5 πŸ”₯

Choose a tag to compare

@poissoncorp poissoncorp released this 02 Feb 09:56
4cd66c4

7.1.5 brings a major overhaul of the Vector Search API with cleaner and more functional methods, adds Embeddings Generation Tasks for automatic vector embedding creation, and introduces new AI agent capabilities.

If you're new to AI in RavenDB, start here for an in-depth introduction.

PyPi link: https://pypi.org/project/ravendb/7.1.5/

Highlights

Embeddings Generation Tasks

New Embeddings Generation Tasks let you automatically generate vector embeddings from document content. Define which document properties to embed, configure chunking strategies, and RavenDB handles the rest - calling the AI provider, caching embeddings, and keeping them in sync as documents change.

Generated embeddings are stored in dedicated collections (@embeddings) and cached (@embeddings-cache) to avoid redundant provider calls. The task processes documents continuously as they change. You can learn more about embedding generation tasks here.

from ravendb.documents.operations.ai import (
    EmbeddingsGenerationConfiguration,
    EmbeddingPathConfiguration,
    ChunkingOptions,
    ChunkingMethod,
    AddEmbeddingsGenerationOperation,
)

# Configure chunking - how text is split before sending to the AI provider
chunking = ChunkingOptions(
    chunking_method=ChunkingMethod.PLAIN_TEXT_SPLIT_LINES,
    max_tokens_per_chunk=2048,
)

# Configure the Embeddings Generation task
config = EmbeddingsGenerationConfiguration(
    name="PostEmbeddings",
    identifier="post-embeddings",
    collection="Posts",
    connection_string_name="openai-embeddings",
    embeddings_path_configurations=[
        EmbeddingPathConfiguration(path="Content", chunking_options=chunking),
        EmbeddingPathConfiguration(path="Title", chunking_options=chunking),
    ],
    chunking_options_for_querying=chunking,  # Used when embedding search terms
)

# Deploy the task
result = store.maintenance.send(AddEmbeddingsGenerationOperation(config))
print(f"Task created with ID: {result.task_id}")

Vector Search API Rework

The Vector Search API has been improved with cleaner, more intuitive method families. There are no breaking changes. The new API provides dedicated methods for each embedding type and use case:

Core methods by embedding type:

# Float32 embeddings (default)
session.query(Product).vector_search("Embedding", vector)

# Int8 quantized embeddings
session.query(Product).vector_search_i8("Embedding", int8_vector)

# Binary (int1) quantized embeddings
session.query(Product).vector_search_i1("Embedding", binary_vector)

# Text-based search (uses embeddings generation task)
session.query(Product).vector_search_text(
    "Embedding",
    "semantic search query",
    embedding_generation_task_identifier="my-embeddings-task"
)

Index field methods for querying pre-indexed embeddings:

# Query against index embedding fields
session.query_index("ProductIndex").vector_search_with_field("EmbeddingField", vector)
session.query_index("ProductIndex").vector_search_with_i8_field("EmbeddingField", int8_vector)
session.query_index("ProductIndex").vector_search_with_i1_field("EmbeddingField", binary_vector)
session.query_index("ProductIndex").vector_search_with_text_field("EmbeddingField", "search text")

Document-based similarity search:

# Find similar documents based on another document's embeddings
session.query(Product).vector_search_text_for_document(
    "Embedding",
    document_id="products/123-A",
    embedding_generation_task_identifier="my-embeddings-task"
)

Base64-encoded vector support:

# For base64-encoded vectors stored in documents
session.query(Product).vector_search_with_base64("Embedding", base64_or_float_vector)
session.query(Product).vector_search_with_base64_i8("Embedding", base64_or_int8_vector)
session.query(Product).vector_search_with_base64_i1("Embedding", base64_or_binary_vector)

All methods support optional parameters: minimum_similarity, number_of_candidates, is_exact, and where applicable, target_quantization.

Deprecated methods: The old vector_search_f32_i8, vector_search_f32_i1, vector_search_text_using_task, and similar methods are now deprecated. Use the new unified methods with appropriate parameters instead.

AI Agent Query Tool Options

Query tools now support additional options for controlling LLM access and initial context:

from ravendb.documents.operations.ai.agents import (
    AiAgentConfiguration,
    AiAgentToolQuery,
    AiAgentToolQueryOptions,
)

config = AiAgentConfiguration(
    identifier="my-agent",
    name="Support Agent",
    system_prompt="You help customers with their orders.",
    query_tools=[
        AiAgentToolQuery(
            name="GetRecentOrders",
            description="Retrieves recent orders for a customer",
            query="from Orders where CustomerId = $customerId order by OrderDate desc limit 5",
            options=AiAgentToolQueryOptions(
                add_to_initial_context=True,  # Run query at conversation start
                allow_model_queries=False,    # Prevent LLM from invoking directly
            ),
        ),
    ],
)

Artificial Actions for AI Agent Conversations

You can now inject artificial tool call responses into AI agent conversations. This is useful for providing context from external systems, simulating tool responses during testing, or pre-populating conversation context:

from ravendb.documents.ai import AiAgentArtificialActionResponse

# Inject an artificial action with its response into the conversation
chat.add_artificial_action_with_response(
    AiAgentArtificialActionResponse(
        tool_id="weather_lookup_123",
        content='{"temperature": 72, "conditions": "sunny"}'
    )
)

# The model will see this as if a tool was called and responded
result = chat.run("response")

Additional Improvements

  • Agent disable support: Agents can now be disabled without deletion via disabled=True in AiAgentConfiguration
  • VectorQuantizer fix: to_int8() and to_int1() now return list[int] instead of bytes for proper JSON serialization
  • AI connection settings: Added embeddings_max_concurrent_batches parameter to all AI provider settings for controlling batch parallelism
  • GenAI Configuration: Added enable_tracing for debugging AI interactions and expiration_in_sec for cache control
  • UpdateGenAiOperation: Now supports a reset parameter to reprocess all documents from scratch
  • Session improvements: Revisions loaded into session now have ignore_changes=True to prevent accidental change tracking; SessionInfo exposes database_name property
  • ChunkingOptions validation: Added validation for overlap tokens - only supported with paragraph-based chunking methods
  • Python 3.13/3.14 compatibility: Fixed compatibility issues with newer Python versions
  • JSONL stream parsing: Added max_empty_lines_in_jsonl_stream convention for handling empty lines in streaming responses

PRs

Full Changelog: 7.1.4...7.1.5