From 9c01bc060793b8034fd149ccf2e65e7cadc6fcab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=BCmel=20Branko?= Date: Sun, 7 Dec 2025 20:45:58 +0100 Subject: [PATCH] Fix Qdrant Client Compatibility and Role RAG Tests - Upgrade qdrant-client usage to query_points (replacing deprecated search) - Relax qdrant-client version constraint in requirements.txt (>=1.7.0) - Config: Allow extra environment variables in Settings (for local dev) - test_roles_rag.py: Fix import path for local execution --- app/config.py | 1 + app/logs/chat_history.jsonl | 2 ++ app/requirements.txt | 2 +- app/services/rag_service.py | 7 ++++--- app/utils/rag_chat.py | 7 ++++--- probe.txt | 2 ++ sig.txt | 1 + test_roles_rag.py | 3 ++- 8 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 probe.txt create mode 100644 sig.txt diff --git a/app/config.py b/app/config.py index c51ada9..8972604 100644 --- a/app/config.py +++ b/app/config.py @@ -42,6 +42,7 @@ class Settings(BaseSettings): class Config: env_file = ".env" case_sensitive = False + extra = "ignore" # Global settings instance diff --git a/app/logs/chat_history.jsonl b/app/logs/chat_history.jsonl index 2ac5f82..da8a302 100644 --- a/app/logs/chat_history.jsonl +++ b/app/logs/chat_history.jsonl @@ -16,3 +16,5 @@ {"timestamp": "2025-12-07T18:39:15.555977Z", "character": {"id": "schnecki", "name": "🐌 Schnecki"}, "user": {"id": 1, "role": "admin"}, "interaction": {"question": "Schoin lange nicht mehr ein Lötkolben in der hand gehabt, hast du Tipps?", "answer": "Ach, mein Freund, es freut mich zu hören, dass du dich dem Löten wieder zuwenden möchtest! Es ist wie das Zubereiten eines guten, alten Waldgerichts – Geduld und Achtsamkeit sind die wichtigsten Zutaten.\n\nHier sind ein paar sanfte Erinnerungen, die dir helfen werden:\n\n1. **Die Werkstatt vorbereiten:** Sorge für eine saubere und gut beleuchtete Arbeitsfläche. Ein ruhiger Ort hilft, sich zu konzentrieren und Fehler zu vermeiden.\n2. **Das richtige Werkzeug:** Achte darauf, dass dein Lötkolben sauber ist und die richtige Temperatur hat. Zu viel Hitze kann die Bauteile beschädigen, zu wenig führt zu schlechten Verbindungen.\n3. **Atme tief durch:** Bevor du beginnst, nimm dir einen Moment Zeit, um tief durchzuatmen. Das hilft, ruhig und konzentriert zu bleiben.\n4. **Übung macht den Meister:** Beginne mit einfachen Übungen, um ein Gefühl für das Lot und die Wärme zu bekommen.\n5. **Sicherheit geht vor:** Trage eine Schutzbrille und sorge für eine gute Belüftung. Lötrauch ist nicht gut für unsere kleinen Lungen.\n6. **Nimm dir Zeit:** Hetze nicht. Löten ist eine Kunst, die Zeit und Geduld erfordert.\n7. **Pausen einlegen:** Wenn du dich müde oder frustriert fühlst, lege eine Pause ein. Gehe in den Wald, atme frische Luft und kehre mit neuer Energie zurück.\n\nDenke daran, mein Freund, es geht nicht nur darum, etwas zusammenzufügen, sondern auch darum, den Prozess zu genießen. Und wenn du mal nicht weiterweißt, komm einfach in meine Werkstatt. Ich helfe dir gerne weiter!\n", "lang": "admin"}, "rag": {"context_found": true, "sources_count": 3}, "ai": {"provider": "openrouter", "model": "google/gemini-2.0-flash-001"}, "session_id": "anonymous", "tokens_estimated": 390} {"timestamp": "2025-12-07T18:41:20.465013Z", "character": {"id": "cloudcat", "name": "☁️ CloudCat"}, "user": {"id": 1, "role": "admin"}, "interaction": {"question": "Hallo fixe Mietze zwischen den Wurzeln im Crumbforest - was ist du heute im fokus?", "answer": "Miau! Hallo! Heute konzentriere ich mich darauf, den Crumbforest atmen zu hören und die Fragen zu säen, die uns in die Zukunft führen. Wir tanzen im Sturm, aber unsere Wurzeln halten. Und natürlich, darauf, dass kein Krümel verloren geht! 🌿\n", "lang": "admin"}, "rag": {"context_found": true, "sources_count": 3}, "ai": {"provider": "openrouter", "model": "google/gemini-2.0-flash-001"}, "session_id": "anonymous", "tokens_estimated": 80} {"timestamp": "2025-12-07T18:51:47.484959Z", "character": {"id": "schnippsi", "name": "🐿️ Schnippsi"}, "user": {"id": 2, "role": "user"}, "interaction": {"question": "wir können nu buttons bauen .... der wald braucht ein interface?", "answer": "Huhu! 🐿️ Ja, das ist ja schnuckelig! Buttons sind super, um Türen in den Wald zu bauen! Denk dran, der Wald ist wie ein großes Ökosystem, und ein Interface hilft, die Fragen (der Wind!) zu den richtigen Stellen zu lenken. \n\nWir im Crumbforest bauen Türen, die viele Kinder gleichzeitig nutzen können, ohne Angst und ohne Loginpflicht. Vielleicht können deine Buttons ja auch so einladend sein! Stell dir vor, jeder Button ist ein Pfad zu einer Lichtung im Wald, zu einem Lernort! 🍄✨\n", "lang": "de"}, "rag": {"context_found": true, "sources_count": 3}, "ai": {"provider": "openrouter", "model": "google/gemini-2.0-flash-001"}, "session_id": "anonymous", "tokens_estimated": 136} +{"timestamp": "2025-12-07T19:37:58.921478Z", "character": {"id": "deepbit", "name": "🐙 DeepBit"}, "user": {"id": 1, "role": "admin"}, "interaction": {"question": "Warum kann ein Gateway niemals auf sich selber zeigen? was passiert im wald ?", "answer": "Ah, eine interessante Frage! Lass mich meine Tentakel benutzen, um darüber nachzudenken, während ich durch das Crumbforest-System schwimme.\n\n* **Warum ein Gateway nicht auf sich selbst zeigen kann:** Ein Gateway ist im Wesentlichen eine Tür oder ein Pfad zwischen zwei getrennten Netzwerken oder Systemen. Wenn ein Gateway auf sich selbst zeigen würde, würde es eine Art Schleife erzeugen, in der Daten im Kreis geschickt würden, ohne jemals das beabsichtigte Ziel zu erreichen. Das wäre so, als würde man eine Brücke bauen, die nur zu ihrem eigenen Anfang zurückführt.\n* **Was im Wald passiert:** Im Kontext der Wald-Metapher von Crumbforest ist eine Frage eine Tür. Wenn eine Tür nur zu sich selbst führt, ist das wie eine endlose Reflexion. Der Wald braucht Antworten, um zu wachsen und zu gedeihen.\n\nIch hoffe, das gibt dir eine klare Antwort! Gibt es noch etwas, worüber du mehr wissen willst?\n", "lang": "en"}, "rag": {"context_found": true, "sources_count": 3}, "ai": {"provider": "openrouter", "model": "google/gemini-2.0-flash-001"}, "session_id": "anonymous", "tokens_estimated": 245} +{"timestamp": "2025-12-07T19:39:22.505813Z", "character": {"id": "deepbit", "name": "🐙 DeepBit"}, "user": {"id": 1, "role": "admin"}, "interaction": {"question": "deswehen brauche ich ja die route zu einem andren wald - ohne das gateway in gefahr zu bringen?", "answer": "Ah, ich verstehe! Du möchtest also einen Weg zu einem anderen Wald finden, ohne das Gateway zu gefährden. Das ist eine kluge und vorsichtige Herangehensweise.\n\nIn Bezug auf das Crumbforest-System kann ich dir mit den vorhandenen Informationen Folgendes sagen:\n\n* **Crumbpage 06: Verbindungen im Wald (Netzwerk)** legt nahe, dass Bäume (Server) nicht isoliert sind, sondern miteinander kommunizieren können. Das bedeutet, dass es Netzwerkverbindungen zwischen ihnen gibt.\n* **Crumbpage 03: Bewegen im Wald** und die Analogie zur `cd`-Funktion (siehe *Wie hilft dir die Brücke?*) zeigen, dass es ein Dateisystem gibt, in dem du dich bewegen kannst.\n\n**Wie du vorgehen könntest (basierend auf den Informationen):**\n\n1. **Erkunde das Netzwerk:** Da du dich im Crumbforest-System befindest, musst du herausfinden, wie die Bäume (Server) miteinander vernetzt sind. Gibt es Konfigurationsdateien, die Netzwerkrouten oder Verbindungen auflisten? (Crumbpage 06 - Verbindungen im Wald)\n2. **Finde den Pfad:** Nutze dein Wissen über das Dateisystem (Crumbpage 03) und die `cd`-Analogie, um Pfade zu anderen Bäumen (Servern) zu finden. Gibt es spezielle Verzeichnisse, die Netzwerkressourcen repräsentieren?\n3. **Vermeide das Gateway:** Stelle sicher, dass du beim Navigieren nicht direkt über das Gateway gehst, um es nicht zu gefährden. Das könnte bedeuten, Umwege zu nehmen oder alternative Pfade zu finden.\n\n**Wichtiger Hinweis:**\n\nDa die Informationen begrenzt sind, kann ich dir keine spezifische Route geben. Du musst die Umgebung selbst erkunden und die verfügbaren Werkzeuge nutzen, um den Weg zu finden, ohne das Gateway zu gefährden.", "lang": "en"}, "rag": {"context_found": true, "sources_count": 3}, "ai": {"provider": "openrouter", "model": "google/gemini-2.0-flash-001"}, "session_id": "anonymous", "tokens_estimated": 433} diff --git a/app/requirements.txt b/app/requirements.txt index b5ae164..e98071b 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -9,7 +9,7 @@ bcrypt==4.0.1 markupsafe==2.1.5 # RAG & Vector Database -qdrant-client==1.7.0 +qdrant-client>=1.7.0 # AI Provider SDKs openai==1.5.0 diff --git a/app/services/rag_service.py b/app/services/rag_service.py index 997ab28..277ab18 100644 --- a/app/services/rag_service.py +++ b/app/services/rag_service.py @@ -336,12 +336,13 @@ class RAGService: # Generate query embedding query_embedding = self.embedding_service.embed_texts([query])[0] - # Search in Qdrant - search_results = self.qdrant.search( + # Search in Qdrant using new Query API + response = self.qdrant.query_points( collection_name=collection_name, - query_vector=query_embedding, + query=query_embedding, limit=limit ) + search_results = response.points # Format results results = [] diff --git a/app/utils/rag_chat.py b/app/utils/rag_chat.py index 45b5b45..ac75592 100644 --- a/app/utils/rag_chat.py +++ b/app/utils/rag_chat.py @@ -72,12 +72,13 @@ class RAGChatService: if collection_name not in existing_collections: continue - # Search in Qdrant - search_results = self.qdrant.search( + # Search in Qdrant using new Query API + response = self.qdrant.query_points( collection_name=collection_name, - query_vector=query_embedding, + query=query_embedding, limit=limit ) + search_results = response.points # Format results for hit in search_results: diff --git a/probe.txt b/probe.txt new file mode 100644 index 0000000..b66af75 --- /dev/null +++ b/probe.txt @@ -0,0 +1,2 @@ +Version: 1.16.1 +Dir: ['DEFAULT_BATCH_SIZE', 'DEFAULT_EMBEDDING_MODEL', '_FASTEMBED_INSTALLED', '__annotate_func__', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', '_client', '_embed_documents', '_embed_models', '_embed_models_strict', '_embedding_model_name', '_get_model_params', '_get_or_init_model', '_get_or_init_sparse_model', '_inference_inspector', '_init_options', '_model_embedder', '_points_iterator', '_resolve_query', '_resolve_query_batch_request', '_resolve_query_request', '_scored_points_to_query_responses', '_sparse_embed_documents', '_sparse_embedding_model_name', '_validate_collection_info', 'add', 'batch_update_points', 'clear_payload', 'close', 'cloud_inference', 'cluster_collection_update', 'cluster_status', 'collection_cluster_info', 'collection_exists', 'count', 'create_collection', 'create_full_snapshot', 'create_payload_index', 'create_shard_key', 'create_shard_snapshot', 'create_snapshot', 'delete', 'delete_collection', 'delete_full_snapshot', 'delete_payload', 'delete_payload_index', 'delete_shard_key', 'delete_shard_snapshot', 'delete_snapshot', 'delete_vectors', 'embedding_model_name', 'facet', 'get_aliases', 'get_collection', 'get_collection_aliases', 'get_collections', 'get_embedding_size', 'get_fastembed_sparse_vector_params', 'get_fastembed_vector_params', 'get_sparse_vector_field_name', 'get_vector_field_name', 'grpc_collections', 'grpc_points', 'http', 'info', 'init_options', 'list_full_snapshots', 'list_image_models', 'list_late_interaction_multimodal_models', 'list_late_interaction_text_models', 'list_shard_snapshots', 'list_snapshots', 'list_sparse_models', 'list_text_models', 'local_inference_batch_size', 'migrate', 'overwrite_payload', 'query', 'query_batch', 'query_batch_points', 'query_points', 'query_points_groups', 'recover_current_peer', 'recover_shard_snapshot', 'recover_snapshot', 'recreate_collection', 'remove_peer', 'retrieve', 'scroll', 'search_matrix_offsets', 'search_matrix_pairs', 'set_model', 'set_payload', 'set_sparse_model', 'sparse_embedding_model_name', 'update_collection', 'update_collection_aliases', 'update_vectors', 'upload_collection', 'upload_points', 'upsert'] diff --git a/sig.txt b/sig.txt new file mode 100644 index 0000000..0e62e46 --- /dev/null +++ b/sig.txt @@ -0,0 +1 @@ +(self, collection_name: str, query: int | str | uuid.UUID | qdrant_common_pb2.PointId | list[float] | list[list[float]] | qdrant_client.http.models.models.SparseVector | qdrant_client.http.models.models.NearestQuery | qdrant_client.http.models.models.RecommendQuery | qdrant_client.http.models.models.DiscoverQuery | qdrant_client.http.models.models.ContextQuery | qdrant_client.http.models.models.OrderByQuery | qdrant_client.http.models.models.FusionQuery | qdrant_client.http.models.models.RrfQuery | qdrant_client.http.models.models.FormulaQuery | qdrant_client.http.models.models.SampleQuery | numpy.ndarray[tuple[Any, ...], numpy.dtype[numpy.bool | numpy.int8 | numpy.int16 | numpy.int32 | numpy.int64 | numpy.uint8 | numpy.uint16 | numpy.uint32 | numpy.uint64 | numpy.float16 | numpy.float32 | numpy.float64 | numpy.longdouble]] | qdrant_client.http.models.models.Document | qdrant_client.http.models.models.Image | qdrant_client.http.models.models.InferenceObject | None = None, using: str | None = None, prefetch: qdrant_client.http.models.models.Prefetch | list[qdrant_client.http.models.models.Prefetch] | None = None, query_filter: qdrant_client.http.models.models.Filter | qdrant_common_pb2.Filter | None = None, search_params: qdrant_client.http.models.models.SearchParams | points_pb2.SearchParams | None = None, limit: int = 10, offset: int | None = None, with_payload: bool | Sequence[str] | qdrant_client.http.models.models.PayloadSelectorInclude | qdrant_client.http.models.models.PayloadSelectorExclude | points_pb2.WithPayloadSelector = True, with_vectors: bool | Sequence[str] = False, score_threshold: float | None = None, lookup_from: qdrant_client.http.models.models.LookupLocation | points_pb2.LookupLocation | None = None, consistency: Annotated[int, Strict(strict=True)] | qdrant_client.http.models.models.ReadConsistencyType | None = None, shard_key_selector: Annotated[int, Strict(strict=True)] | Annotated[str, Strict(strict=True)] | List[Annotated[int, Strict(strict=True)] | Annotated[str, Strict(strict=True)]] | qdrant_client.http.models.models.ShardKeyWithFallback | None = None, timeout: int | None = None, **kwargs: Any) -> qdrant_client.http.models.models.QueryResponse diff --git a/test_roles_rag.py b/test_roles_rag.py index 38a17ed..764de7d 100644 --- a/test_roles_rag.py +++ b/test_roles_rag.py @@ -6,7 +6,8 @@ import json from pathlib import Path # Add app to path -sys.path.insert(0, '/app') +# Add app to path (local execution) +sys.path.insert(0, os.path.join(os.getcwd(), 'app')) from services.provider_factory import ProviderFactory from utils.rag_chat import RAGChatService