Repository: Quansight/ragna (python)

Issue:
Make Chat.prepare idempotent
Currently we require the chat to not be prepared when calling `await chat.prepare()`

https://github.com/Quansight/ragna/blob/7071cf4fdaae03b89c837f6034dbac217dd81d72/ragna/core/_rag.py#L187-L193

That was a reasonable choice when every chat needed to be prepared. However, now with the `MetadataFilter` (#456 and following on the `corpus-dev` branch), we have regular use cases where we the chat is already prepared:

https://github.com/Quansight/ragna/blob/3dc586fa485b79b1397e7739a2921d7992a60064/ragna/core/_rag.py#L161

https://github.com/Quansight/ragna/blob/3dc586fa485b79b1397e7739a2921d7992a60064/ragna/core/_rag.py#L247-L248

This makes it rather quirky during development where you might switch between different startegies, e.g.

```python
for input in [None, metadata_filter, documents]:
    chat = Rag().chat(input=input, ...)
    if not (input is None or isinstance(input, MetadataFilter)):
        await chat.prepare()
```

Thus, I propose we remove the error from above and just make the method a no-op in case the chat is already prepared. This has no downside for the user and avoids the extra checks.

Pull request description:
make Chat.prepare idempotent
Closes #480.

Patch:
diff --git a/ragna/core/_rag.py b/ragna/core/_rag.py
index 304206f..d21c64d 100644
--- a/ragna/core/_rag.py
+++ b/ragna/core/_rag.py
@@ -181,26 +181,18 @@ class Chat:
 
         Returns:
             Welcome message.
-
-        Raises:
-            ragna.core.RagnaException: If chat is already
-                [`prepare`][ragna.core.Chat.prepare]d.
         """
-        if self._prepared or self.documents is None:
-            raise RagnaException(
-                "Chat is already prepared",
-                chat=self,
-                http_status_code=400,
-                http_detail=RagnaException.EVENT,
-            )
-
-        await self._run(self.source_storage.store, self.documents)
-        self._prepared = True
-
         welcome = Message(
             content="How can I help you with the documents?",
             role=MessageRole.SYSTEM,
         )
+
+        if self._prepared:
+            return welcome
+
+        await self._run(self.source_storage.store, self.documents)
+        self._prepared = True
+
         self._messages.append(welcome)
         return welcome
 


Test patch:
diff --git a/tests/core/test_e2e.py b/tests/core/test_e2e.py
index 1495c76..504ff0a 100644
--- a/tests/core/test_e2e.py
+++ b/tests/core/test_e2e.py
@@ -11,16 +11,12 @@ from ragna.source_storages import RagnaDemoSourceStorage
 @pytest.mark.parametrize("input_type", ["corpus", "metadata_filter", "documents"])
 def test_e2e(tmp_local_root, input_type):
     async def main(*, input, source_storage, assistant):
-        chat = Rag().chat(
+        async with Rag().chat(
             input=input,
             source_storage=source_storage,
             assistant=assistant,
-        )
-
-        if not (input is None or isinstance(input, MetadataFilter)):
-            await chat.prepare()
-
-        return await chat.answer("?")
+        ) as chat:
+            return await chat.answer("?")
 
     document_root = tmp_local_root / "documents"
     document_root.mkdir()