sunholo 0.140.4__tar.gz → 0.140.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sunholo-0.140.4/src/sunholo.egg-info → sunholo-0.140.5}/PKG-INFO +1 -1
- {sunholo-0.140.4 → sunholo-0.140.5}/pyproject.toml +1 -1
- sunholo-0.140.5/src/sunholo/agents/chat_history.py +523 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/flask/vac_routes.py +17 -15
- {sunholo-0.140.4 → sunholo-0.140.5/src/sunholo.egg-info}/PKG-INFO +1 -1
- sunholo-0.140.4/src/sunholo/agents/chat_history.py +0 -245
- {sunholo-0.140.4 → sunholo-0.140.5}/LICENSE.txt +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/MANIFEST.in +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/README.md +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/setup.cfg +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/dispatch_to_qa.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/fastapi/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/fastapi/base.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/fastapi/qna_routes.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/flask/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/flask/base.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/langserve.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/pubsub.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/route.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/special_commands.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/agents/swagger.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/archive/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/archive/archive.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/auth/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/auth/gcloud.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/auth/refresh.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/auth/run.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/azure/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/azure/auth.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/azure/blobs.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/azure/event_grid.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/bots/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/bots/discord.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/bots/github_webhook.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/bots/webapp.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/chunker/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/chunker/azure.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/chunker/doc_handling.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/chunker/encode_metadata.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/chunker/images.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/chunker/loaders.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/chunker/message_data.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/chunker/pdfs.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/chunker/process_chunker_data.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/chunker/publish.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/chunker/pubsub.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/chunker/splitter.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/cli/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/cli/chat_vac.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/cli/cli.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/cli/cli_init.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/cli/configs.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/cli/deploy.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/cli/embedder.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/cli/merge_texts.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/cli/run_proxy.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/cli/sun_rich.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/cli/swagger.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/cli/vertex.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/components/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/components/llm.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/components/retriever.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/components/vectorstore.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/custom_logging.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/alloydb.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/alloydb_client.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/database.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/lancedb.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/sql/sb/create_function.sql +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/sql/sb/create_function_time.sql +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/sql/sb/create_table.sql +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/sql/sb/delete_source_row.sql +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/sql/sb/return_sources.sql +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/sql/sb/setup.sql +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/static_dbs.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/database/uuid.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/discovery_engine/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/discovery_engine/chunker_handler.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/discovery_engine/cli.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/discovery_engine/create_new.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/discovery_engine/discovery_engine_client.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/embedder/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/embedder/embed_chunk.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/embedder/embed_metadata.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/excel/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/excel/plugin.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/gcs/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/gcs/add_file.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/gcs/download_folder.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/gcs/download_gcs_text.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/gcs/download_url.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/gcs/extract_and_sign.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/gcs/metadata.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/genai/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/genai/file_handling.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/genai/genaiv2.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/genai/images.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/genai/init.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/genai/process_funcs_cls.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/genai/safety.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/invoke/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/invoke/async_class.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/invoke/direct_vac_func.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/invoke/invoke_vac_utils.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/langchain_types.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/langfuse/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/langfuse/callback.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/langfuse/evals.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/langfuse/prompts.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/llamaindex/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/llamaindex/get_files.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/llamaindex/import_files.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/llamaindex/llamaindex_class.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/llamaindex/user_history.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/lookup/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/lookup/model_lookup.yaml +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/mcp/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/mcp/cli.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/ollama/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/ollama/ollama_images.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/pubsub/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/pubsub/process_pubsub.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/pubsub/pubsub_manager.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/qna/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/qna/parsers.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/qna/retry.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/senses/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/senses/stream_voice.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/streaming/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/streaming/content_buffer.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/streaming/langserve.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/streaming/stream_lookup.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/streaming/streaming.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/summarise/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/summarise/summarise.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/agent/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/agent/agent_service.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/agent/app.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/agent/my_log.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/agent/tools/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/agent/tools/your_agent.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/agent/vac_service.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/project/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/project/app.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/project/my_log.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/project/vac_service.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/system_services/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/system_services/app.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/templates/system_services/my_log.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/terraform/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/terraform/tfvars_editor.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/tools/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/tools/web_browser.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/api_key.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/big_context.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/config.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/config_class.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/config_schema.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/gcp.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/gcp_project.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/mime.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/parsers.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/timedelta.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/user_ids.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/utils/version.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/vertex/__init__.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/vertex/extensions_call.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/vertex/extensions_class.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/vertex/genai_functions.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/vertex/init.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/vertex/memory_tools.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/vertex/safety.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo/vertex/type_dict_to_json.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo.egg-info/SOURCES.txt +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo.egg-info/dependency_links.txt +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo.egg-info/entry_points.txt +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo.egg-info/requires.txt +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/src/sunholo.egg-info/top_level.txt +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/tests/test_async.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/tests/test_async_genai2.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/tests/test_chat_history.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/tests/test_config.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/tests/test_genai2.py +0 -0
- {sunholo-0.140.4 → sunholo-0.140.5}/tests/test_unstructured.py +0 -0
@@ -0,0 +1,523 @@
|
|
1
|
+
import json
|
2
|
+
from ..custom_logging import log
|
3
|
+
import time
|
4
|
+
import hashlib
|
5
|
+
from functools import lru_cache
|
6
|
+
from typing import List, Tuple, Optional
|
7
|
+
|
8
|
+
|
9
|
+
class ChatHistoryCache:
|
10
|
+
"""
|
11
|
+
Incremental cache for chat history processing.
|
12
|
+
|
13
|
+
Caches processed message pairs and only processes new messages
|
14
|
+
when the chat history is extended.
|
15
|
+
"""
|
16
|
+
|
17
|
+
def __init__(self, max_cache_size: int = 1000):
|
18
|
+
self.cache = {}
|
19
|
+
self.max_cache_size = max_cache_size
|
20
|
+
|
21
|
+
def _get_cache_key(self, chat_history: List[dict]) -> str:
|
22
|
+
"""Generate a cache key based on the chat history content."""
|
23
|
+
# Use the hash of the serialized chat history for the key
|
24
|
+
# Only hash the first few and last few messages to balance performance vs accuracy
|
25
|
+
if len(chat_history) <= 10:
|
26
|
+
content = str(chat_history)
|
27
|
+
else:
|
28
|
+
# Hash first 5 and last 5 messages + length
|
29
|
+
content = str(chat_history[:5] + chat_history[-5:] + [len(chat_history)])
|
30
|
+
|
31
|
+
return hashlib.md5(content.encode()).hexdigest()
|
32
|
+
|
33
|
+
def _find_cached_prefix(self, current_history: List[dict]) -> Tuple[Optional[List[Tuple]], int]:
|
34
|
+
"""
|
35
|
+
Find the longest cached prefix of the current chat history.
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
Tuple of (cached_pairs, cache_length) or (None, 0) if no cache found
|
39
|
+
"""
|
40
|
+
current_length = len(current_history)
|
41
|
+
|
42
|
+
# Check for cached versions of prefixes, starting from longest
|
43
|
+
for cache_length in range(current_length - 1, 0, -1):
|
44
|
+
prefix = current_history[:cache_length]
|
45
|
+
cache_key = self._get_cache_key(prefix)
|
46
|
+
|
47
|
+
if cache_key in self.cache:
|
48
|
+
cached_data = self.cache[cache_key]
|
49
|
+
cached_pairs = cached_data['pairs']
|
50
|
+
|
51
|
+
# Verify the cache is still valid by checking a few messages
|
52
|
+
if self._verify_cache_validity(prefix, cached_data['original_history']):
|
53
|
+
return cached_pairs, cache_length
|
54
|
+
else:
|
55
|
+
# Cache is stale, remove it
|
56
|
+
del self.cache[cache_key]
|
57
|
+
|
58
|
+
return None, 0
|
59
|
+
|
60
|
+
def _verify_cache_validity(self, current_prefix: List[dict], cached_prefix: List[dict]) -> bool:
|
61
|
+
"""Quick verification that cached data is still valid."""
|
62
|
+
if len(current_prefix) != len(cached_prefix):
|
63
|
+
return False
|
64
|
+
|
65
|
+
# Check first and last few messages for equality
|
66
|
+
check_indices = [0, -1] if len(current_prefix) >= 2 else [0]
|
67
|
+
|
68
|
+
for i in check_indices:
|
69
|
+
if current_prefix[i] != cached_prefix[i]:
|
70
|
+
return False
|
71
|
+
|
72
|
+
return True
|
73
|
+
|
74
|
+
def extract_chat_history_incremental(self, chat_history: List[dict]) -> List[Tuple]:
|
75
|
+
"""
|
76
|
+
Extract chat history with incremental caching.
|
77
|
+
|
78
|
+
Args:
|
79
|
+
chat_history: List of chat message dictionaries
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
List of (human_message, ai_message) tuples
|
83
|
+
"""
|
84
|
+
if not chat_history:
|
85
|
+
return []
|
86
|
+
|
87
|
+
# Try to find cached prefix
|
88
|
+
cached_pairs, cache_length = self._find_cached_prefix(chat_history)
|
89
|
+
|
90
|
+
if cached_pairs is not None:
|
91
|
+
log.debug(f"Found cached pairs for {cache_length} messages, processing {len(chat_history) - cache_length} new messages")
|
92
|
+
|
93
|
+
# Process only the new messages
|
94
|
+
new_messages = chat_history[cache_length:]
|
95
|
+
new_pairs = self._process_new_messages(new_messages, cached_pairs)
|
96
|
+
|
97
|
+
# Combine cached and new pairs
|
98
|
+
all_pairs = cached_pairs + new_pairs
|
99
|
+
else:
|
100
|
+
log.debug(f"No cache found, processing all {len(chat_history)} messages")
|
101
|
+
# Process all messages from scratch
|
102
|
+
all_pairs = self._extract_chat_history_full(chat_history)
|
103
|
+
|
104
|
+
# Cache the result
|
105
|
+
self._update_cache(chat_history, all_pairs)
|
106
|
+
|
107
|
+
return all_pairs
|
108
|
+
|
109
|
+
def _process_new_messages(self, new_messages: List[dict], cached_pairs: List[Tuple]) -> List[Tuple]:
|
110
|
+
"""
|
111
|
+
Process only the new messages, considering the state from cached pairs.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
new_messages: New messages to process
|
115
|
+
cached_pairs: Previously processed message pairs
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
List of new message pairs
|
119
|
+
"""
|
120
|
+
if not new_messages:
|
121
|
+
return []
|
122
|
+
|
123
|
+
new_pairs = []
|
124
|
+
|
125
|
+
# Determine if we're waiting for a bot response based on cached pairs
|
126
|
+
waiting_for_bot = True
|
127
|
+
if cached_pairs:
|
128
|
+
last_pair = cached_pairs[-1]
|
129
|
+
# If last pair has both human and AI message, we're ready for a new human message
|
130
|
+
waiting_for_bot = not (last_pair[0] and last_pair[1])
|
131
|
+
|
132
|
+
# If we ended with an unpaired human message, get it
|
133
|
+
last_human_message = ""
|
134
|
+
if cached_pairs and waiting_for_bot:
|
135
|
+
last_human_message = cached_pairs[-1][0]
|
136
|
+
|
137
|
+
# Process new messages
|
138
|
+
for message in new_messages:
|
139
|
+
try:
|
140
|
+
is_human_msg = is_human(message)
|
141
|
+
content = create_message_element(message)
|
142
|
+
|
143
|
+
if is_human_msg:
|
144
|
+
last_human_message = content
|
145
|
+
waiting_for_bot = True
|
146
|
+
else: # Bot message
|
147
|
+
if waiting_for_bot and last_human_message:
|
148
|
+
new_pairs.append((last_human_message, content))
|
149
|
+
last_human_message = ""
|
150
|
+
waiting_for_bot = False
|
151
|
+
# If not waiting for bot or no human message, this is an orphaned bot message
|
152
|
+
|
153
|
+
except (KeyError, TypeError) as e:
|
154
|
+
log.warning(f"Error processing new message: {e}")
|
155
|
+
continue
|
156
|
+
|
157
|
+
return new_pairs
|
158
|
+
|
159
|
+
def _extract_chat_history_full(self, chat_history: List[dict]) -> List[Tuple]:
|
160
|
+
"""Full extraction when no cache is available."""
|
161
|
+
# Use the optimized version from before
|
162
|
+
paired_messages = []
|
163
|
+
|
164
|
+
# Handle initial bot message
|
165
|
+
start_idx = 0
|
166
|
+
if chat_history and is_bot(chat_history[0]):
|
167
|
+
try:
|
168
|
+
first_message = chat_history[0]
|
169
|
+
blank_element = ""
|
170
|
+
bot_element = create_message_element(first_message)
|
171
|
+
paired_messages.append((blank_element, bot_element))
|
172
|
+
start_idx = 1
|
173
|
+
except (KeyError, TypeError):
|
174
|
+
pass
|
175
|
+
|
176
|
+
# Process remaining messages
|
177
|
+
last_human_message = ""
|
178
|
+
for i in range(start_idx, len(chat_history)):
|
179
|
+
message = chat_history[i]
|
180
|
+
|
181
|
+
try:
|
182
|
+
is_human_msg = is_human(message)
|
183
|
+
content = create_message_element(message)
|
184
|
+
|
185
|
+
if is_human_msg:
|
186
|
+
last_human_message = content
|
187
|
+
else: # Bot message
|
188
|
+
if last_human_message:
|
189
|
+
paired_messages.append((last_human_message, content))
|
190
|
+
last_human_message = ""
|
191
|
+
|
192
|
+
except (KeyError, TypeError) as e:
|
193
|
+
log.warning(f"Error processing message {i}: {e}")
|
194
|
+
continue
|
195
|
+
|
196
|
+
return paired_messages
|
197
|
+
|
198
|
+
def _update_cache(self, chat_history: List[dict], pairs: List[Tuple]):
|
199
|
+
"""Update cache with new result."""
|
200
|
+
# Only cache if the history is of reasonable size
|
201
|
+
if len(chat_history) < 2:
|
202
|
+
return
|
203
|
+
|
204
|
+
cache_key = self._get_cache_key(chat_history)
|
205
|
+
|
206
|
+
# Implement simple LRU by removing oldest entries
|
207
|
+
if len(self.cache) >= self.max_cache_size:
|
208
|
+
# Remove 20% of oldest entries
|
209
|
+
remove_count = self.max_cache_size // 5
|
210
|
+
oldest_keys = list(self.cache.keys())[:remove_count]
|
211
|
+
for key in oldest_keys:
|
212
|
+
del self.cache[key]
|
213
|
+
|
214
|
+
self.cache[cache_key] = {
|
215
|
+
'pairs': pairs,
|
216
|
+
'original_history': chat_history.copy(), # Store copy for validation
|
217
|
+
'timestamp': time.time()
|
218
|
+
}
|
219
|
+
|
220
|
+
log.debug(f"Cached {len(pairs)} pairs for history of length {len(chat_history)}")
|
221
|
+
|
222
|
+
def clear_cache(self):
|
223
|
+
"""Clear the entire cache."""
|
224
|
+
self.cache.clear()
|
225
|
+
log.info("Chat history cache cleared")
|
226
|
+
|
227
|
+
|
228
|
+
# Global cache instance
|
229
|
+
_chat_history_cache = ChatHistoryCache()
|
230
|
+
|
231
|
+
|
232
|
+
def extract_chat_history_with_cache(chat_history: List[dict] = None) -> List[Tuple]:
|
233
|
+
"""
|
234
|
+
Main function to replace the original extract_chat_history.
|
235
|
+
|
236
|
+
Uses incremental caching for better performance with growing chat histories.
|
237
|
+
"""
|
238
|
+
if not chat_history:
|
239
|
+
log.debug("No chat history found")
|
240
|
+
return []
|
241
|
+
|
242
|
+
return _chat_history_cache.extract_chat_history_incremental(chat_history)
|
243
|
+
|
244
|
+
|
245
|
+
# Async version that wraps the cached version
|
246
|
+
async def extract_chat_history_async_cached(chat_history: List[dict] = None) -> List[Tuple]:
|
247
|
+
"""
|
248
|
+
Async version that uses the cache and runs in a thread pool if needed.
|
249
|
+
"""
|
250
|
+
import asyncio
|
251
|
+
|
252
|
+
if not chat_history:
|
253
|
+
return []
|
254
|
+
|
255
|
+
# For very large histories, run in thread pool to avoid blocking
|
256
|
+
if len(chat_history) > 1000:
|
257
|
+
loop = asyncio.get_event_loop()
|
258
|
+
return await loop.run_in_executor(
|
259
|
+
None,
|
260
|
+
extract_chat_history_with_cache,
|
261
|
+
chat_history
|
262
|
+
)
|
263
|
+
else:
|
264
|
+
# For smaller histories, just run directly
|
265
|
+
return extract_chat_history_with_cache(chat_history)
|
266
|
+
|
267
|
+
|
268
|
+
# Utility function to warm up the cache
|
269
|
+
def warm_up_cache(chat_histories: List[List[dict]]):
|
270
|
+
"""
|
271
|
+
Pre-populate cache with common chat histories.
|
272
|
+
|
273
|
+
Args:
|
274
|
+
chat_histories: List of chat history lists to cache
|
275
|
+
"""
|
276
|
+
for history in chat_histories:
|
277
|
+
extract_chat_history_with_cache(history)
|
278
|
+
|
279
|
+
log.info(f"Warmed up cache with {len(chat_histories)} chat histories")
|
280
|
+
|
281
|
+
|
282
|
+
async def extract_chat_history_async(chat_history=None):
|
283
|
+
"""
|
284
|
+
Extracts paired chat history between human and AI messages.
|
285
|
+
|
286
|
+
For this lightweight processing, we use a simpler approach that minimizes overhead.
|
287
|
+
|
288
|
+
Args:
|
289
|
+
chat_history (list): List of chat messages.
|
290
|
+
|
291
|
+
Returns:
|
292
|
+
list: List of tuples with paired human and AI messages.
|
293
|
+
"""
|
294
|
+
if not chat_history:
|
295
|
+
log.info("No chat history found")
|
296
|
+
return []
|
297
|
+
|
298
|
+
log.info(f"Extracting chat history: {chat_history}")
|
299
|
+
paired_messages = []
|
300
|
+
|
301
|
+
# Handle special case of initial bot message
|
302
|
+
if chat_history and is_bot(chat_history[0]):
|
303
|
+
first_message = chat_history[0]
|
304
|
+
log.info(f"Extracting first_message: {first_message}")
|
305
|
+
blank_human_message = {"name": "Human", "content": "", "embeds": []}
|
306
|
+
|
307
|
+
# Since create_message_element is so lightweight, we don't need async here
|
308
|
+
blank_element = create_message_element(blank_human_message)
|
309
|
+
bot_element = create_message_element(first_message)
|
310
|
+
|
311
|
+
paired_messages.append((blank_element, bot_element))
|
312
|
+
chat_history = chat_history[1:]
|
313
|
+
|
314
|
+
# Pre-process all messages in one batch (more efficient than one-by-one)
|
315
|
+
message_types = []
|
316
|
+
message_contents = []
|
317
|
+
|
318
|
+
for message in chat_history:
|
319
|
+
is_human_msg = is_human(message)
|
320
|
+
is_bot_msg = is_bot(message)
|
321
|
+
|
322
|
+
# Extract content for all messages at once
|
323
|
+
content = create_message_element(message)
|
324
|
+
|
325
|
+
message_types.append((is_human_msg, is_bot_msg))
|
326
|
+
message_contents.append(content)
|
327
|
+
|
328
|
+
# Pair messages efficiently
|
329
|
+
last_human_message = ""
|
330
|
+
for i, ((is_human_msg, is_bot_msg), content) in enumerate(zip(message_types, message_contents)):
|
331
|
+
if is_human_msg:
|
332
|
+
last_human_message = content
|
333
|
+
log.info(f"Extracted human message: {last_human_message}")
|
334
|
+
elif is_bot_msg:
|
335
|
+
ai_message = content
|
336
|
+
log.info(f"Extracted AI message: {ai_message}")
|
337
|
+
paired_messages.append((last_human_message, ai_message))
|
338
|
+
last_human_message = ""
|
339
|
+
|
340
|
+
log.info(f"Paired messages: {paired_messages}")
|
341
|
+
return paired_messages
|
342
|
+
|
343
|
+
|
344
|
+
def extract_chat_history(chat_history=None):
|
345
|
+
"""
|
346
|
+
Extracts paired chat history between human and AI messages.
|
347
|
+
|
348
|
+
This function takes a chat history and returns a list of pairs of messages,
|
349
|
+
where each pair consists of a human message followed by the corresponding AI response.
|
350
|
+
|
351
|
+
Args:
|
352
|
+
chat_history (list): List of chat messages.
|
353
|
+
|
354
|
+
Returns:
|
355
|
+
list: List of tuples with paired human and AI messages.
|
356
|
+
|
357
|
+
Example:
|
358
|
+
```python
|
359
|
+
chat_history = [
|
360
|
+
{"name": "Human", "text": "Hello, AI!"},
|
361
|
+
{"name": "AI", "text": "Hello, Human! How can I help you today?"}
|
362
|
+
]
|
363
|
+
paired_messages = extract_chat_history(chat_history)
|
364
|
+
print(paired_messages)
|
365
|
+
# Output: [("Hello, AI!", "Hello, Human! How can I help you today?")]
|
366
|
+
```
|
367
|
+
"""
|
368
|
+
if not chat_history:
|
369
|
+
log.info("No chat history found")
|
370
|
+
return []
|
371
|
+
|
372
|
+
log.info(f"Extracting chat history: {chat_history}")
|
373
|
+
paired_messages = []
|
374
|
+
|
375
|
+
first_message = chat_history[0]
|
376
|
+
log.info(f"Extracting first_message: {first_message}")
|
377
|
+
if is_bot(first_message):
|
378
|
+
blank_human_message = {"name": "Human", "content": "", "embeds": []}
|
379
|
+
paired_messages.append((create_message_element(blank_human_message),
|
380
|
+
create_message_element(first_message)))
|
381
|
+
chat_history = chat_history[1:]
|
382
|
+
|
383
|
+
last_human_message = ""
|
384
|
+
for message in chat_history:
|
385
|
+
log.info(f"Extracing message: {message}")
|
386
|
+
if is_human(message):
|
387
|
+
last_human_message = create_message_element(message)
|
388
|
+
log.info(f"Extracted human message: {last_human_message}")
|
389
|
+
elif is_bot(message):
|
390
|
+
ai_message = create_message_element(message)
|
391
|
+
log.info(f"Extracted AI message: {ai_message}")
|
392
|
+
paired_messages.append((last_human_message, ai_message))
|
393
|
+
last_human_message = ""
|
394
|
+
|
395
|
+
log.info(f"Paired messages: {paired_messages}")
|
396
|
+
|
397
|
+
return paired_messages
|
398
|
+
|
399
|
+
def embeds_to_json(message: dict):
|
400
|
+
"""
|
401
|
+
Converts the 'embeds' field in a message to a JSON string.
|
402
|
+
|
403
|
+
Args:
|
404
|
+
message (dict): The message containing the 'embeds' field.
|
405
|
+
|
406
|
+
Returns:
|
407
|
+
str: JSON string representation of the 'embeds' field or an empty string if no embeds are found.
|
408
|
+
|
409
|
+
Example:
|
410
|
+
```python
|
411
|
+
message = {"embeds": [{"type": "image", "url": "https://example.com/image.png"}]}
|
412
|
+
json_string = embeds_to_json(message)
|
413
|
+
print(json_string)
|
414
|
+
# Output: '[{"type": "image", "url": "https://example.com/image.png"}]'
|
415
|
+
```
|
416
|
+
"""
|
417
|
+
if 'embeds' in message and len(message['embeds']) > 0:
|
418
|
+
return json.dumps(message.get("embeds"))
|
419
|
+
else:
|
420
|
+
return ""
|
421
|
+
|
422
|
+
def create_message_element(message: dict):
|
423
|
+
"""
|
424
|
+
Extracts the main content of a message.
|
425
|
+
|
426
|
+
Args:
|
427
|
+
message (dict): The message to extract content from.
|
428
|
+
|
429
|
+
Returns:
|
430
|
+
str: The text or content of the message.
|
431
|
+
|
432
|
+
Raises:
|
433
|
+
KeyError: If neither 'content' nor 'text' fields are found.
|
434
|
+
|
435
|
+
Example:
|
436
|
+
```python
|
437
|
+
message = {"text": "Hello, AI!"}
|
438
|
+
content = create_message_element(message)
|
439
|
+
print(content)
|
440
|
+
# Output: 'Hello, AI!'
|
441
|
+
```
|
442
|
+
"""
|
443
|
+
if 'text' in message: # This is a Slack or Google Chat message
|
444
|
+
log.info(f"Found text element - {message['text']}")
|
445
|
+
return message['text']
|
446
|
+
elif 'content' in message: # Discord or OpenAI history message
|
447
|
+
log.info(f"Found content element - {message['content']}")
|
448
|
+
return message['content']
|
449
|
+
else:
|
450
|
+
raise KeyError(f"Could not extract 'content' or 'text' element from message: {message}, {type(message)}")
|
451
|
+
|
452
|
+
def is_human(message: dict):
|
453
|
+
"""
|
454
|
+
Checks if a message was sent by a human.
|
455
|
+
|
456
|
+
Args:
|
457
|
+
message (dict): The message to check.
|
458
|
+
|
459
|
+
Returns:
|
460
|
+
bool: True if the message was sent by a human, otherwise False.
|
461
|
+
|
462
|
+
Example:
|
463
|
+
```python
|
464
|
+
message = {"name": "Human"}
|
465
|
+
print(is_human(message))
|
466
|
+
# Output: True
|
467
|
+
```
|
468
|
+
"""
|
469
|
+
if 'name' in message:
|
470
|
+
return message["name"] == "Human"
|
471
|
+
elif 'sender' in message: # Google Chat
|
472
|
+
return message['sender']['type'] == 'HUMAN'
|
473
|
+
elif 'role' in message:
|
474
|
+
return message['role'] == 'user'
|
475
|
+
else:
|
476
|
+
# Slack: Check for the 'user' field and absence of 'bot_id' field
|
477
|
+
return 'user' in message and 'bot_id' not in message
|
478
|
+
|
479
|
+
def is_bot(message: dict):
|
480
|
+
"""
|
481
|
+
Checks if a message was sent by a bot.
|
482
|
+
|
483
|
+
Args:
|
484
|
+
message (dict): The message to check.
|
485
|
+
|
486
|
+
Returns:
|
487
|
+
bool: True if the message was sent by a bot, otherwise False.
|
488
|
+
|
489
|
+
Example:
|
490
|
+
```python
|
491
|
+
message = {"name": "AI"}
|
492
|
+
print(is_bot(message))
|
493
|
+
# Output: True
|
494
|
+
```
|
495
|
+
"""
|
496
|
+
return not is_human(message)
|
497
|
+
|
498
|
+
def is_ai(message: dict):
|
499
|
+
"""
|
500
|
+
Checks if a message was specifically sent by an AI.
|
501
|
+
|
502
|
+
Args:
|
503
|
+
message (dict): The message to check.
|
504
|
+
|
505
|
+
Returns:
|
506
|
+
bool: True if the message was sent by an AI, otherwise False.
|
507
|
+
|
508
|
+
Example:
|
509
|
+
```python
|
510
|
+
message = {"name": "AI"}
|
511
|
+
print(is_ai(message))
|
512
|
+
# Output: True
|
513
|
+
```
|
514
|
+
"""
|
515
|
+
if 'name' in message:
|
516
|
+
return message["name"] == "AI"
|
517
|
+
elif 'sender' in message: # Google Chat
|
518
|
+
return message['sender']['type'] == 'BOT'
|
519
|
+
elif 'role' in message:
|
520
|
+
return message['role'] == 'assistant'
|
521
|
+
else:
|
522
|
+
return 'bot_id' in message # Slack
|
523
|
+
|
@@ -7,8 +7,8 @@ from functools import partial
|
|
7
7
|
import inspect
|
8
8
|
import asyncio
|
9
9
|
|
10
|
-
from ...agents import
|
11
|
-
from ..chat_history import
|
10
|
+
from ...agents import handle_special_commands
|
11
|
+
from ..chat_history import extract_chat_history_with_cache, extract_chat_history_async_cached
|
12
12
|
from ...qna.parsers import parse_output
|
13
13
|
from ...streaming import start_streaming_chat, start_streaming_chat_async
|
14
14
|
from ...archive import archive_qa
|
@@ -58,12 +58,18 @@ if __name__ == "__main__":
|
|
58
58
|
```
|
59
59
|
|
60
60
|
"""
|
61
|
-
def __init__(self, app,
|
61
|
+
def __init__(self, app,
|
62
|
+
stream_interpreter: callable,
|
63
|
+
vac_interpreter:callable=None,
|
64
|
+
additional_routes:dict=None,
|
65
|
+
async_stream:bool=False,
|
66
|
+
add_langfuse_eval:bool=True):
|
62
67
|
self.app = app
|
63
68
|
self.stream_interpreter = stream_interpreter
|
64
69
|
self.vac_interpreter = vac_interpreter or partial(self.vac_interpreter_default)
|
65
70
|
self.additional_routes = additional_routes if additional_routes is not None else []
|
66
71
|
self.async_stream = async_stream
|
72
|
+
self.add_langfuse_eval = add_langfuse_eval
|
67
73
|
self.register_routes()
|
68
74
|
|
69
75
|
|
@@ -550,7 +556,8 @@ if __name__ == "__main__":
|
|
550
556
|
else:
|
551
557
|
log.info(f"User message: {user_message}")
|
552
558
|
|
553
|
-
paired_messages =
|
559
|
+
paired_messages = extract_chat_history_with_cache(chat_history)
|
560
|
+
|
554
561
|
command_response = handle_special_commands(user_message, vector_name, paired_messages)
|
555
562
|
|
556
563
|
if command_response is not None:
|
@@ -694,10 +701,10 @@ if __name__ == "__main__":
|
|
694
701
|
|
695
702
|
trace = None
|
696
703
|
span = None
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
704
|
+
if self.add_langfuse_eval:
|
705
|
+
trace_id = data.get('trace_id')
|
706
|
+
trace = self.create_langfuse_trace(request, vector_name, trace_id)
|
707
|
+
log.info(f"Using existing langfuse trace: {trace_id}")
|
701
708
|
|
702
709
|
#config, _ = load_config("config/llm_config.yaml")
|
703
710
|
try:
|
@@ -721,7 +728,7 @@ if __name__ == "__main__":
|
|
721
728
|
vector_name = data.pop('vector_name', vector_name)
|
722
729
|
data.pop('trace_id', None) # to ensure not in kwargs
|
723
730
|
|
724
|
-
paired_messages =
|
731
|
+
paired_messages = extract_chat_history_with_cache(chat_history)
|
725
732
|
|
726
733
|
all_input = {'user_input': user_input,
|
727
734
|
'vector_name': vector_name,
|
@@ -737,15 +744,10 @@ if __name__ == "__main__":
|
|
737
744
|
metadata=vac_config.configs_by_kind,
|
738
745
|
input = all_input
|
739
746
|
)
|
740
|
-
command_response = handle_special_commands(user_input, vector_name, paired_messages)
|
741
|
-
if command_response is not None:
|
742
|
-
if trace:
|
743
|
-
trace.update(output=jsonify(command_response))
|
744
747
|
|
745
748
|
return {
|
746
749
|
"trace": trace,
|
747
750
|
"span": span,
|
748
|
-
"command_response": command_response,
|
749
751
|
"all_input": all_input,
|
750
752
|
"vac_config": vac_config
|
751
753
|
}
|
@@ -789,7 +791,7 @@ if __name__ == "__main__":
|
|
789
791
|
data.pop('trace_id', None) # to ensure not in kwargs
|
790
792
|
|
791
793
|
# Task 3: Process chat history
|
792
|
-
chat_history_task = asyncio.create_task(
|
794
|
+
chat_history_task = asyncio.create_task(extract_chat_history_async_cached(chat_history))
|
793
795
|
tasks.append(chat_history_task)
|
794
796
|
|
795
797
|
# Await all tasks concurrently
|