agno 1.8.0__py3-none-any.whl → 2.0.0a1__py3-none-any.whl
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.
- agno/__init__.py +8 -0
- agno/agent/__init__.py +19 -27
- agno/agent/agent.py +2781 -4126
- agno/api/agent.py +9 -65
- agno/api/api.py +5 -46
- agno/api/evals.py +6 -17
- agno/api/os.py +17 -0
- agno/api/routes.py +6 -41
- agno/api/schemas/__init__.py +9 -0
- agno/api/schemas/agent.py +5 -21
- agno/api/schemas/evals.py +7 -16
- agno/api/schemas/os.py +14 -0
- agno/api/schemas/team.py +5 -21
- agno/api/schemas/utils.py +21 -0
- agno/api/schemas/workflows.py +11 -7
- agno/api/settings.py +53 -0
- agno/api/team.py +9 -64
- agno/api/workflow.py +28 -0
- agno/cloud/aws/base.py +214 -0
- agno/cloud/aws/s3/__init__.py +2 -0
- agno/cloud/aws/s3/api_client.py +43 -0
- agno/cloud/aws/s3/bucket.py +195 -0
- agno/cloud/aws/s3/object.py +57 -0
- agno/db/__init__.py +24 -0
- agno/db/base.py +245 -0
- agno/db/dynamo/__init__.py +3 -0
- agno/db/dynamo/dynamo.py +1749 -0
- agno/db/dynamo/schemas.py +278 -0
- agno/db/dynamo/utils.py +684 -0
- agno/db/firestore/__init__.py +3 -0
- agno/db/firestore/firestore.py +1438 -0
- agno/db/firestore/schemas.py +130 -0
- agno/db/firestore/utils.py +278 -0
- agno/db/gcs_json/__init__.py +3 -0
- agno/db/gcs_json/gcs_json_db.py +1001 -0
- agno/db/gcs_json/utils.py +194 -0
- agno/db/in_memory/__init__.py +3 -0
- agno/db/in_memory/in_memory_db.py +888 -0
- agno/db/in_memory/utils.py +172 -0
- agno/db/json/__init__.py +3 -0
- agno/db/json/json_db.py +1051 -0
- agno/db/json/utils.py +196 -0
- agno/db/migrations/v1_to_v2.py +162 -0
- agno/db/mongo/__init__.py +3 -0
- agno/db/mongo/mongo.py +1417 -0
- agno/db/mongo/schemas.py +77 -0
- agno/db/mongo/utils.py +204 -0
- agno/db/mysql/__init__.py +3 -0
- agno/db/mysql/mysql.py +1719 -0
- agno/db/mysql/schemas.py +124 -0
- agno/db/mysql/utils.py +298 -0
- agno/db/postgres/__init__.py +3 -0
- agno/db/postgres/postgres.py +1720 -0
- agno/db/postgres/schemas.py +124 -0
- agno/db/postgres/utils.py +281 -0
- agno/db/redis/__init__.py +3 -0
- agno/db/redis/redis.py +1371 -0
- agno/db/redis/schemas.py +109 -0
- agno/db/redis/utils.py +288 -0
- agno/db/schemas/__init__.py +3 -0
- agno/db/schemas/evals.py +33 -0
- agno/db/schemas/knowledge.py +40 -0
- agno/db/schemas/memory.py +46 -0
- agno/db/singlestore/__init__.py +3 -0
- agno/db/singlestore/schemas.py +116 -0
- agno/db/singlestore/singlestore.py +1722 -0
- agno/db/singlestore/utils.py +327 -0
- agno/db/sqlite/__init__.py +3 -0
- agno/db/sqlite/schemas.py +119 -0
- agno/db/sqlite/sqlite.py +1680 -0
- agno/db/sqlite/utils.py +269 -0
- agno/db/utils.py +88 -0
- agno/eval/__init__.py +14 -0
- agno/eval/accuracy.py +142 -43
- agno/eval/performance.py +88 -23
- agno/eval/reliability.py +73 -20
- agno/eval/utils.py +23 -13
- agno/integrations/discord/__init__.py +3 -0
- agno/{app → integrations}/discord/client.py +10 -10
- agno/knowledge/__init__.py +2 -2
- agno/{document → knowledge}/chunking/agentic.py +2 -2
- agno/{document → knowledge}/chunking/document.py +2 -2
- agno/{document → knowledge}/chunking/fixed.py +3 -3
- agno/{document → knowledge}/chunking/markdown.py +2 -2
- agno/{document → knowledge}/chunking/recursive.py +2 -2
- agno/{document → knowledge}/chunking/row.py +2 -2
- agno/knowledge/chunking/semantic.py +59 -0
- agno/knowledge/chunking/strategy.py +121 -0
- agno/knowledge/content.py +74 -0
- agno/knowledge/document/__init__.py +5 -0
- agno/{document → knowledge/document}/base.py +12 -2
- agno/knowledge/embedder/__init__.py +5 -0
- agno/{embedder → knowledge/embedder}/aws_bedrock.py +127 -1
- agno/{embedder → knowledge/embedder}/azure_openai.py +65 -1
- agno/{embedder → knowledge/embedder}/base.py +6 -0
- agno/{embedder → knowledge/embedder}/cohere.py +72 -1
- agno/{embedder → knowledge/embedder}/fastembed.py +17 -1
- agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
- agno/{embedder → knowledge/embedder}/google.py +74 -1
- agno/{embedder → knowledge/embedder}/huggingface.py +36 -2
- agno/{embedder → knowledge/embedder}/jina.py +48 -2
- agno/knowledge/embedder/langdb.py +22 -0
- agno/knowledge/embedder/mistral.py +139 -0
- agno/{embedder → knowledge/embedder}/nebius.py +1 -1
- agno/{embedder → knowledge/embedder}/ollama.py +54 -3
- agno/knowledge/embedder/openai.py +223 -0
- agno/{embedder → knowledge/embedder}/sentence_transformer.py +16 -1
- agno/{embedder → knowledge/embedder}/together.py +1 -1
- agno/{embedder → knowledge/embedder}/voyageai.py +49 -1
- agno/knowledge/knowledge.py +1515 -0
- agno/knowledge/reader/__init__.py +7 -0
- agno/{document → knowledge}/reader/arxiv_reader.py +32 -4
- agno/knowledge/reader/base.py +88 -0
- agno/{document → knowledge}/reader/csv_reader.py +68 -15
- agno/knowledge/reader/docx_reader.py +83 -0
- agno/{document → knowledge}/reader/firecrawl_reader.py +42 -21
- agno/knowledge/reader/gcs_reader.py +67 -0
- agno/{document → knowledge}/reader/json_reader.py +30 -9
- agno/{document → knowledge}/reader/markdown_reader.py +36 -9
- agno/{document → knowledge}/reader/pdf_reader.py +79 -21
- agno/knowledge/reader/reader_factory.py +275 -0
- agno/knowledge/reader/s3_reader.py +171 -0
- agno/{document → knowledge}/reader/text_reader.py +31 -10
- agno/knowledge/reader/url_reader.py +84 -0
- agno/knowledge/reader/web_search_reader.py +389 -0
- agno/{document → knowledge}/reader/website_reader.py +37 -10
- agno/knowledge/reader/wikipedia_reader.py +59 -0
- agno/knowledge/reader/youtube_reader.py +78 -0
- agno/knowledge/remote_content/remote_content.py +88 -0
- agno/{reranker → knowledge/reranker}/base.py +1 -1
- agno/{reranker → knowledge/reranker}/cohere.py +2 -2
- agno/{reranker → knowledge/reranker}/infinity.py +2 -2
- agno/{reranker → knowledge/reranker}/sentence_transformer.py +2 -2
- agno/knowledge/types.py +30 -0
- agno/knowledge/utils.py +169 -0
- agno/media.py +2 -2
- agno/memory/__init__.py +2 -10
- agno/memory/manager.py +1003 -148
- agno/models/aimlapi/__init__.py +2 -2
- agno/models/aimlapi/aimlapi.py +6 -6
- agno/models/anthropic/claude.py +129 -82
- agno/models/aws/bedrock.py +107 -175
- agno/models/aws/claude.py +64 -18
- agno/models/azure/ai_foundry.py +73 -23
- agno/models/base.py +347 -287
- agno/models/cerebras/cerebras.py +84 -27
- agno/models/cohere/chat.py +106 -98
- agno/models/dashscope/dashscope.py +14 -5
- agno/models/google/gemini.py +123 -53
- agno/models/groq/groq.py +97 -35
- agno/models/huggingface/huggingface.py +92 -27
- agno/models/ibm/watsonx.py +72 -13
- agno/models/litellm/chat.py +85 -13
- agno/models/message.py +38 -144
- agno/models/meta/llama.py +85 -49
- agno/models/metrics.py +120 -0
- agno/models/mistral/mistral.py +90 -21
- agno/models/ollama/__init__.py +0 -2
- agno/models/ollama/chat.py +84 -46
- agno/models/openai/chat.py +135 -27
- agno/models/openai/responses.py +233 -115
- agno/models/perplexity/perplexity.py +26 -2
- agno/models/portkey/portkey.py +0 -7
- agno/models/response.py +14 -8
- agno/models/utils.py +20 -0
- agno/models/vercel/__init__.py +2 -2
- agno/models/vercel/v0.py +1 -1
- agno/models/vllm/__init__.py +2 -2
- agno/models/vllm/vllm.py +3 -3
- agno/models/xai/xai.py +10 -10
- agno/os/__init__.py +3 -0
- agno/os/app.py +393 -0
- agno/os/auth.py +47 -0
- agno/os/config.py +103 -0
- agno/os/interfaces/agui/__init__.py +3 -0
- agno/os/interfaces/agui/agui.py +31 -0
- agno/{app/agui/async_router.py → os/interfaces/agui/router.py} +16 -16
- agno/{app → os/interfaces}/agui/utils.py +65 -28
- agno/os/interfaces/base.py +21 -0
- agno/os/interfaces/slack/__init__.py +3 -0
- agno/{app/slack/async_router.py → os/interfaces/slack/router.py} +3 -5
- agno/os/interfaces/slack/slack.py +33 -0
- agno/os/interfaces/whatsapp/__init__.py +3 -0
- agno/{app/whatsapp/async_router.py → os/interfaces/whatsapp/router.py} +4 -7
- agno/os/interfaces/whatsapp/whatsapp.py +30 -0
- agno/os/router.py +843 -0
- agno/os/routers/__init__.py +3 -0
- agno/os/routers/evals/__init__.py +3 -0
- agno/os/routers/evals/evals.py +204 -0
- agno/os/routers/evals/schemas.py +142 -0
- agno/os/routers/evals/utils.py +161 -0
- agno/os/routers/knowledge/__init__.py +3 -0
- agno/os/routers/knowledge/knowledge.py +413 -0
- agno/os/routers/knowledge/schemas.py +118 -0
- agno/os/routers/memory/__init__.py +3 -0
- agno/os/routers/memory/memory.py +179 -0
- agno/os/routers/memory/schemas.py +58 -0
- agno/os/routers/metrics/__init__.py +3 -0
- agno/os/routers/metrics/metrics.py +58 -0
- agno/os/routers/metrics/schemas.py +47 -0
- agno/os/routers/session/__init__.py +3 -0
- agno/os/routers/session/session.py +163 -0
- agno/os/schema.py +892 -0
- agno/{app/playground → os}/settings.py +8 -15
- agno/os/utils.py +270 -0
- agno/reasoning/azure_ai_foundry.py +4 -4
- agno/reasoning/deepseek.py +4 -4
- agno/reasoning/default.py +6 -11
- agno/reasoning/groq.py +4 -4
- agno/reasoning/helpers.py +4 -6
- agno/reasoning/ollama.py +4 -4
- agno/reasoning/openai.py +4 -4
- agno/run/{response.py → agent.py} +144 -72
- agno/run/base.py +44 -58
- agno/run/cancel.py +83 -0
- agno/run/team.py +133 -77
- agno/run/workflow.py +537 -12
- agno/session/__init__.py +10 -0
- agno/session/agent.py +244 -0
- agno/session/summary.py +225 -0
- agno/session/team.py +262 -0
- agno/{storage/session/v2 → session}/workflow.py +47 -24
- agno/team/__init__.py +15 -16
- agno/team/team.py +2967 -4243
- agno/tools/agentql.py +14 -5
- agno/tools/airflow.py +9 -4
- agno/tools/api.py +7 -3
- agno/tools/apify.py +2 -46
- agno/tools/arxiv.py +8 -3
- agno/tools/aws_lambda.py +7 -5
- agno/tools/aws_ses.py +7 -1
- agno/tools/baidusearch.py +4 -1
- agno/tools/bitbucket.py +4 -4
- agno/tools/brandfetch.py +14 -11
- agno/tools/bravesearch.py +4 -1
- agno/tools/brightdata.py +42 -22
- agno/tools/browserbase.py +13 -4
- agno/tools/calcom.py +12 -10
- agno/tools/calculator.py +10 -27
- agno/tools/cartesia.py +18 -13
- agno/tools/{clickup_tool.py → clickup.py} +12 -25
- agno/tools/confluence.py +71 -18
- agno/tools/crawl4ai.py +7 -1
- agno/tools/csv_toolkit.py +9 -8
- agno/tools/dalle.py +18 -11
- agno/tools/daytona.py +13 -16
- agno/tools/decorator.py +6 -3
- agno/tools/desi_vocal.py +16 -7
- agno/tools/discord.py +11 -8
- agno/tools/docker.py +30 -42
- agno/tools/duckdb.py +34 -53
- agno/tools/duckduckgo.py +8 -7
- agno/tools/e2b.py +62 -62
- agno/tools/eleven_labs.py +35 -28
- agno/tools/email.py +4 -1
- agno/tools/evm.py +7 -1
- agno/tools/exa.py +19 -14
- agno/tools/fal.py +29 -29
- agno/tools/file.py +9 -8
- agno/tools/financial_datasets.py +25 -44
- agno/tools/firecrawl.py +22 -22
- agno/tools/function.py +68 -17
- agno/tools/giphy.py +22 -10
- agno/tools/github.py +48 -126
- agno/tools/gmail.py +46 -62
- agno/tools/google_bigquery.py +7 -6
- agno/tools/google_maps.py +11 -26
- agno/tools/googlesearch.py +7 -2
- agno/tools/googlesheets.py +21 -17
- agno/tools/hackernews.py +9 -5
- agno/tools/jina.py +5 -4
- agno/tools/jira.py +18 -9
- agno/tools/knowledge.py +31 -32
- agno/tools/linear.py +18 -33
- agno/tools/linkup.py +5 -1
- agno/tools/local_file_system.py +8 -5
- agno/tools/lumalab.py +31 -19
- agno/tools/mem0.py +18 -12
- agno/tools/memori.py +14 -10
- agno/tools/mlx_transcribe.py +3 -2
- agno/tools/models/azure_openai.py +32 -14
- agno/tools/models/gemini.py +58 -31
- agno/tools/models/groq.py +29 -20
- agno/tools/models/nebius.py +27 -11
- agno/tools/models_labs.py +39 -15
- agno/tools/moviepy_video.py +7 -6
- agno/tools/neo4j.py +134 -0
- agno/tools/newspaper.py +7 -2
- agno/tools/newspaper4k.py +8 -3
- agno/tools/openai.py +57 -26
- agno/tools/openbb.py +12 -11
- agno/tools/opencv.py +62 -46
- agno/tools/openweather.py +14 -12
- agno/tools/pandas.py +11 -3
- agno/tools/postgres.py +4 -12
- agno/tools/pubmed.py +4 -1
- agno/tools/python.py +9 -22
- agno/tools/reasoning.py +35 -27
- agno/tools/reddit.py +11 -26
- agno/tools/replicate.py +54 -41
- agno/tools/resend.py +4 -1
- agno/tools/scrapegraph.py +15 -14
- agno/tools/searxng.py +10 -23
- agno/tools/serpapi.py +6 -3
- agno/tools/serper.py +13 -4
- agno/tools/shell.py +9 -2
- agno/tools/slack.py +12 -11
- agno/tools/sleep.py +3 -2
- agno/tools/spider.py +24 -4
- agno/tools/sql.py +7 -6
- agno/tools/tavily.py +6 -4
- agno/tools/telegram.py +12 -4
- agno/tools/todoist.py +11 -31
- agno/tools/toolkit.py +1 -1
- agno/tools/trafilatura.py +22 -6
- agno/tools/trello.py +9 -22
- agno/tools/twilio.py +10 -3
- agno/tools/user_control_flow.py +6 -1
- agno/tools/valyu.py +34 -5
- agno/tools/visualization.py +19 -28
- agno/tools/webbrowser.py +4 -3
- agno/tools/webex.py +11 -7
- agno/tools/website.py +15 -46
- agno/tools/webtools.py +12 -4
- agno/tools/whatsapp.py +5 -9
- agno/tools/wikipedia.py +20 -13
- agno/tools/x.py +14 -13
- agno/tools/yfinance.py +13 -40
- agno/tools/youtube.py +26 -20
- agno/tools/zendesk.py +7 -2
- agno/tools/zep.py +10 -7
- agno/tools/zoom.py +10 -9
- agno/utils/common.py +1 -19
- agno/utils/events.py +95 -118
- agno/utils/knowledge.py +29 -0
- agno/utils/location.py +2 -2
- agno/utils/log.py +2 -2
- agno/utils/mcp.py +11 -5
- agno/utils/media.py +39 -0
- agno/utils/message.py +12 -1
- agno/utils/models/claude.py +6 -4
- agno/utils/models/mistral.py +8 -7
- agno/utils/models/schema_utils.py +3 -3
- agno/utils/pprint.py +33 -32
- agno/utils/print_response/agent.py +779 -0
- agno/utils/print_response/team.py +1565 -0
- agno/utils/print_response/workflow.py +1451 -0
- agno/utils/prompts.py +14 -14
- agno/utils/reasoning.py +87 -0
- agno/utils/response.py +42 -42
- agno/utils/string.py +8 -22
- agno/utils/team.py +50 -0
- agno/utils/timer.py +2 -2
- agno/vectordb/base.py +33 -21
- agno/vectordb/cassandra/cassandra.py +287 -23
- agno/vectordb/chroma/chromadb.py +482 -59
- agno/vectordb/clickhouse/clickhousedb.py +270 -63
- agno/vectordb/couchbase/couchbase.py +309 -29
- agno/vectordb/lancedb/lance_db.py +360 -21
- agno/vectordb/langchaindb/__init__.py +5 -0
- agno/vectordb/langchaindb/langchaindb.py +145 -0
- agno/vectordb/lightrag/__init__.py +5 -0
- agno/vectordb/lightrag/lightrag.py +374 -0
- agno/vectordb/llamaindex/llamaindexdb.py +127 -0
- agno/vectordb/milvus/milvus.py +242 -32
- agno/vectordb/mongodb/mongodb.py +200 -24
- agno/vectordb/pgvector/pgvector.py +319 -37
- agno/vectordb/pineconedb/pineconedb.py +221 -27
- agno/vectordb/qdrant/qdrant.py +356 -14
- agno/vectordb/singlestore/singlestore.py +286 -29
- agno/vectordb/surrealdb/surrealdb.py +187 -7
- agno/vectordb/upstashdb/upstashdb.py +342 -26
- agno/vectordb/weaviate/weaviate.py +227 -165
- agno/workflow/__init__.py +17 -13
- agno/workflow/{v2/condition.py → condition.py} +135 -32
- agno/workflow/{v2/loop.py → loop.py} +115 -28
- agno/workflow/{v2/parallel.py → parallel.py} +138 -108
- agno/workflow/{v2/router.py → router.py} +133 -32
- agno/workflow/{v2/step.py → step.py} +200 -42
- agno/workflow/{v2/steps.py → steps.py} +147 -66
- agno/workflow/types.py +482 -0
- agno/workflow/workflow.py +2394 -696
- agno-2.0.0a1.dist-info/METADATA +355 -0
- agno-2.0.0a1.dist-info/RECORD +514 -0
- agno/agent/metrics.py +0 -107
- agno/api/app.py +0 -35
- agno/api/playground.py +0 -92
- agno/api/schemas/app.py +0 -12
- agno/api/schemas/playground.py +0 -22
- agno/api/schemas/user.py +0 -35
- agno/api/schemas/workspace.py +0 -46
- agno/api/user.py +0 -160
- agno/api/workflows.py +0 -33
- agno/api/workspace.py +0 -175
- agno/app/agui/__init__.py +0 -3
- agno/app/agui/app.py +0 -17
- agno/app/agui/sync_router.py +0 -120
- agno/app/base.py +0 -186
- agno/app/discord/__init__.py +0 -3
- agno/app/fastapi/__init__.py +0 -3
- agno/app/fastapi/app.py +0 -107
- agno/app/fastapi/async_router.py +0 -457
- agno/app/fastapi/sync_router.py +0 -448
- agno/app/playground/app.py +0 -228
- agno/app/playground/async_router.py +0 -1050
- agno/app/playground/deploy.py +0 -249
- agno/app/playground/operator.py +0 -183
- agno/app/playground/schemas.py +0 -220
- agno/app/playground/serve.py +0 -55
- agno/app/playground/sync_router.py +0 -1042
- agno/app/playground/utils.py +0 -46
- agno/app/settings.py +0 -15
- agno/app/slack/__init__.py +0 -3
- agno/app/slack/app.py +0 -19
- agno/app/slack/sync_router.py +0 -92
- agno/app/utils.py +0 -54
- agno/app/whatsapp/__init__.py +0 -3
- agno/app/whatsapp/app.py +0 -15
- agno/app/whatsapp/sync_router.py +0 -197
- agno/cli/auth_server.py +0 -249
- agno/cli/config.py +0 -274
- agno/cli/console.py +0 -88
- agno/cli/credentials.py +0 -23
- agno/cli/entrypoint.py +0 -571
- agno/cli/operator.py +0 -357
- agno/cli/settings.py +0 -96
- agno/cli/ws/ws_cli.py +0 -817
- agno/constants.py +0 -13
- agno/document/__init__.py +0 -5
- agno/document/chunking/semantic.py +0 -45
- agno/document/chunking/strategy.py +0 -31
- agno/document/reader/__init__.py +0 -5
- agno/document/reader/base.py +0 -47
- agno/document/reader/docx_reader.py +0 -60
- agno/document/reader/gcs/pdf_reader.py +0 -44
- agno/document/reader/s3/pdf_reader.py +0 -59
- agno/document/reader/s3/text_reader.py +0 -63
- agno/document/reader/url_reader.py +0 -59
- agno/document/reader/youtube_reader.py +0 -58
- agno/embedder/__init__.py +0 -5
- agno/embedder/langdb.py +0 -80
- agno/embedder/mistral.py +0 -82
- agno/embedder/openai.py +0 -78
- agno/file/__init__.py +0 -5
- agno/file/file.py +0 -16
- agno/file/local/csv.py +0 -32
- agno/file/local/txt.py +0 -19
- agno/infra/app.py +0 -240
- agno/infra/base.py +0 -144
- agno/infra/context.py +0 -20
- agno/infra/db_app.py +0 -52
- agno/infra/resource.py +0 -205
- agno/infra/resources.py +0 -55
- agno/knowledge/agent.py +0 -698
- agno/knowledge/arxiv.py +0 -33
- agno/knowledge/combined.py +0 -36
- agno/knowledge/csv.py +0 -144
- agno/knowledge/csv_url.py +0 -124
- agno/knowledge/document.py +0 -223
- agno/knowledge/docx.py +0 -137
- agno/knowledge/firecrawl.py +0 -34
- agno/knowledge/gcs/__init__.py +0 -0
- agno/knowledge/gcs/base.py +0 -39
- agno/knowledge/gcs/pdf.py +0 -125
- agno/knowledge/json.py +0 -137
- agno/knowledge/langchain.py +0 -71
- agno/knowledge/light_rag.py +0 -273
- agno/knowledge/llamaindex.py +0 -66
- agno/knowledge/markdown.py +0 -154
- agno/knowledge/pdf.py +0 -164
- agno/knowledge/pdf_bytes.py +0 -42
- agno/knowledge/pdf_url.py +0 -148
- agno/knowledge/s3/__init__.py +0 -0
- agno/knowledge/s3/base.py +0 -64
- agno/knowledge/s3/pdf.py +0 -33
- agno/knowledge/s3/text.py +0 -34
- agno/knowledge/text.py +0 -141
- agno/knowledge/url.py +0 -46
- agno/knowledge/website.py +0 -179
- agno/knowledge/wikipedia.py +0 -32
- agno/knowledge/youtube.py +0 -35
- agno/memory/agent.py +0 -423
- agno/memory/classifier.py +0 -104
- agno/memory/db/__init__.py +0 -5
- agno/memory/db/base.py +0 -42
- agno/memory/db/mongodb.py +0 -189
- agno/memory/db/postgres.py +0 -203
- agno/memory/db/sqlite.py +0 -193
- agno/memory/memory.py +0 -22
- agno/memory/row.py +0 -36
- agno/memory/summarizer.py +0 -201
- agno/memory/summary.py +0 -19
- agno/memory/team.py +0 -415
- agno/memory/v2/__init__.py +0 -2
- agno/memory/v2/db/__init__.py +0 -1
- agno/memory/v2/db/base.py +0 -42
- agno/memory/v2/db/firestore.py +0 -339
- agno/memory/v2/db/mongodb.py +0 -196
- agno/memory/v2/db/postgres.py +0 -214
- agno/memory/v2/db/redis.py +0 -187
- agno/memory/v2/db/schema.py +0 -54
- agno/memory/v2/db/sqlite.py +0 -209
- agno/memory/v2/manager.py +0 -437
- agno/memory/v2/memory.py +0 -1097
- agno/memory/v2/schema.py +0 -55
- agno/memory/v2/summarizer.py +0 -215
- agno/memory/workflow.py +0 -38
- agno/models/ollama/tools.py +0 -430
- agno/models/qwen/__init__.py +0 -5
- agno/playground/__init__.py +0 -10
- agno/playground/deploy.py +0 -3
- agno/playground/playground.py +0 -3
- agno/playground/serve.py +0 -3
- agno/playground/settings.py +0 -3
- agno/reranker/__init__.py +0 -0
- agno/run/v2/__init__.py +0 -0
- agno/run/v2/workflow.py +0 -567
- agno/storage/__init__.py +0 -0
- agno/storage/agent/__init__.py +0 -0
- agno/storage/agent/dynamodb.py +0 -1
- agno/storage/agent/json.py +0 -1
- agno/storage/agent/mongodb.py +0 -1
- agno/storage/agent/postgres.py +0 -1
- agno/storage/agent/singlestore.py +0 -1
- agno/storage/agent/sqlite.py +0 -1
- agno/storage/agent/yaml.py +0 -1
- agno/storage/base.py +0 -60
- agno/storage/dynamodb.py +0 -673
- agno/storage/firestore.py +0 -297
- agno/storage/gcs_json.py +0 -261
- agno/storage/in_memory.py +0 -234
- agno/storage/json.py +0 -237
- agno/storage/mongodb.py +0 -328
- agno/storage/mysql.py +0 -685
- agno/storage/postgres.py +0 -682
- agno/storage/redis.py +0 -336
- agno/storage/session/__init__.py +0 -16
- agno/storage/session/agent.py +0 -64
- agno/storage/session/team.py +0 -63
- agno/storage/session/v2/__init__.py +0 -5
- agno/storage/session/workflow.py +0 -61
- agno/storage/singlestore.py +0 -606
- agno/storage/sqlite.py +0 -646
- agno/storage/workflow/__init__.py +0 -0
- agno/storage/workflow/mongodb.py +0 -1
- agno/storage/workflow/postgres.py +0 -1
- agno/storage/workflow/sqlite.py +0 -1
- agno/storage/yaml.py +0 -241
- agno/tools/thinking.py +0 -73
- agno/utils/defaults.py +0 -57
- agno/utils/filesystem.py +0 -39
- agno/utils/git.py +0 -52
- agno/utils/json_io.py +0 -30
- agno/utils/load_env.py +0 -19
- agno/utils/py_io.py +0 -19
- agno/utils/pyproject.py +0 -18
- agno/utils/resource_filter.py +0 -31
- agno/workflow/v2/__init__.py +0 -21
- agno/workflow/v2/types.py +0 -357
- agno/workflow/v2/workflow.py +0 -3312
- agno/workspace/__init__.py +0 -0
- agno/workspace/config.py +0 -325
- agno/workspace/enums.py +0 -6
- agno/workspace/helpers.py +0 -52
- agno/workspace/operator.py +0 -757
- agno/workspace/settings.py +0 -158
- agno-1.8.0.dist-info/METADATA +0 -979
- agno-1.8.0.dist-info/RECORD +0 -565
- agno-1.8.0.dist-info/entry_points.txt +0 -3
- /agno/{app → db/migrations}/__init__.py +0 -0
- /agno/{app/playground/__init__.py → db/schemas/metrics.py} +0 -0
- /agno/{cli → integrations}/__init__.py +0 -0
- /agno/{cli/ws → knowledge/chunking}/__init__.py +0 -0
- /agno/{document/chunking → knowledge/remote_content}/__init__.py +0 -0
- /agno/{document/reader/gcs → knowledge/reranker}/__init__.py +0 -0
- /agno/{document/reader/s3 → os/interfaces}/__init__.py +0 -0
- /agno/{app → os/interfaces}/slack/security.py +0 -0
- /agno/{app → os/interfaces}/whatsapp/security.py +0 -0
- /agno/{file/local → utils/print_response}/__init__.py +0 -0
- /agno/{infra → vectordb/llamaindex}/__init__.py +0 -0
- {agno-1.8.0.dist-info → agno-2.0.0a1.dist-info}/WHEEL +0 -0
- {agno-1.8.0.dist-info → agno-2.0.0a1.dist-info}/licenses/LICENSE +0 -0
- {agno-1.8.0.dist-info → agno-2.0.0a1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,888 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from datetime import date, datetime, timedelta, timezone
|
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from agno.db.base import BaseDb, SessionType
|
|
7
|
+
from agno.db.in_memory.utils import (
|
|
8
|
+
apply_sorting,
|
|
9
|
+
calculate_date_metrics,
|
|
10
|
+
fetch_all_sessions_data,
|
|
11
|
+
get_dates_to_calculate_metrics_for,
|
|
12
|
+
)
|
|
13
|
+
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
14
|
+
from agno.db.schemas.knowledge import KnowledgeRow
|
|
15
|
+
from agno.db.schemas.memory import UserMemory
|
|
16
|
+
from agno.session import AgentSession, Session, TeamSession, WorkflowSession
|
|
17
|
+
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class InMemoryDb(BaseDb):
|
|
21
|
+
def __init__(self):
|
|
22
|
+
"""Interface for in-memory storage."""
|
|
23
|
+
super().__init__()
|
|
24
|
+
|
|
25
|
+
# Initialize in-memory storage dictionaries
|
|
26
|
+
self._sessions: List[Dict[str, Any]] = []
|
|
27
|
+
self._memories: List[Dict[str, Any]] = []
|
|
28
|
+
self._metrics: List[Dict[str, Any]] = []
|
|
29
|
+
self._eval_runs: List[Dict[str, Any]] = []
|
|
30
|
+
self._knowledge: List[Dict[str, Any]] = []
|
|
31
|
+
|
|
32
|
+
# -- Session methods --
|
|
33
|
+
|
|
34
|
+
def delete_session(self, session_id: str) -> bool:
|
|
35
|
+
"""Delete a session from in-memory storage.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
session_id (str): The ID of the session to delete.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
bool: True if the session was deleted, False otherwise.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
Exception: If an error occurs during deletion.
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
original_count = len(self._sessions)
|
|
48
|
+
self._sessions = [s for s in self._sessions if s.get("session_id") != session_id]
|
|
49
|
+
|
|
50
|
+
if len(self._sessions) < original_count:
|
|
51
|
+
log_debug(f"Successfully deleted session with session_id: {session_id}")
|
|
52
|
+
return True
|
|
53
|
+
else:
|
|
54
|
+
log_debug(f"No session found to delete with session_id: {session_id}")
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
log_error(f"Error deleting session: {e}")
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
def delete_sessions(self, session_ids: List[str]) -> None:
|
|
62
|
+
"""Delete multiple sessions from in-memory storage.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
session_ids (List[str]): The IDs of the sessions to delete.
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
Exception: If an error occurs during deletion.
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
self._sessions = [s for s in self._sessions if s.get("session_id") not in session_ids]
|
|
72
|
+
log_debug(f"Successfully deleted sessions with ids: {session_ids}")
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
log_error(f"Error deleting sessions: {e}")
|
|
76
|
+
|
|
77
|
+
def get_session(
|
|
78
|
+
self,
|
|
79
|
+
session_id: str,
|
|
80
|
+
session_type: SessionType,
|
|
81
|
+
user_id: Optional[str] = None,
|
|
82
|
+
deserialize: Optional[bool] = True,
|
|
83
|
+
) -> Optional[Union[AgentSession, TeamSession, WorkflowSession, Dict[str, Any]]]:
|
|
84
|
+
"""Read a session from in-memory storage.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
session_id (str): The ID of the session to read.
|
|
88
|
+
session_type (SessionType): The type of the session to read.
|
|
89
|
+
user_id (Optional[str]): The ID of the user to read the session for.
|
|
90
|
+
deserialize (Optional[bool]): Whether to deserialize the session.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Union[Session, Dict[str, Any], None]:
|
|
94
|
+
- When deserialize=True: Session object
|
|
95
|
+
- When deserialize=False: Session dictionary
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
Exception: If an error occurs while reading the session.
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
for session_data in self._sessions:
|
|
102
|
+
if session_data.get("session_id") == session_id:
|
|
103
|
+
if user_id is not None and session_data.get("user_id") != user_id:
|
|
104
|
+
continue
|
|
105
|
+
session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
|
|
106
|
+
if session_data.get("session_type") != session_type_value:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
if not deserialize:
|
|
110
|
+
return session_data
|
|
111
|
+
|
|
112
|
+
if session_type == SessionType.AGENT:
|
|
113
|
+
return AgentSession.from_dict(session_data)
|
|
114
|
+
elif session_type == SessionType.TEAM:
|
|
115
|
+
return TeamSession.from_dict(session_data)
|
|
116
|
+
else:
|
|
117
|
+
return WorkflowSession.from_dict(session_data)
|
|
118
|
+
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
except Exception as e:
|
|
122
|
+
import traceback
|
|
123
|
+
|
|
124
|
+
traceback.print_exc()
|
|
125
|
+
log_error(f"Exception reading session: {e}")
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
def get_sessions(
|
|
129
|
+
self,
|
|
130
|
+
session_type: SessionType,
|
|
131
|
+
user_id: Optional[str] = None,
|
|
132
|
+
component_id: Optional[str] = None,
|
|
133
|
+
session_name: Optional[str] = None,
|
|
134
|
+
start_timestamp: Optional[int] = None,
|
|
135
|
+
end_timestamp: Optional[int] = None,
|
|
136
|
+
limit: Optional[int] = None,
|
|
137
|
+
page: Optional[int] = None,
|
|
138
|
+
sort_by: Optional[str] = None,
|
|
139
|
+
sort_order: Optional[str] = None,
|
|
140
|
+
deserialize: Optional[bool] = True,
|
|
141
|
+
) -> Union[List[Session], Tuple[List[Dict[str, Any]], int]]:
|
|
142
|
+
"""Get all sessions from in-memory storage with filtering and pagination.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
session_type (SessionType): The type of the sessions to read.
|
|
146
|
+
user_id (Optional[str]): The ID of the user to read the sessions for.
|
|
147
|
+
component_id (Optional[str]): The ID of the component to read the sessions for.
|
|
148
|
+
session_name (Optional[str]): The name of the session to read.
|
|
149
|
+
start_timestamp (Optional[int]): The start timestamp of the sessions to read.
|
|
150
|
+
end_timestamp (Optional[int]): The end timestamp of the sessions to read.
|
|
151
|
+
limit (Optional[int]): The limit of the sessions to read.
|
|
152
|
+
page (Optional[int]): The page of the sessions to read.
|
|
153
|
+
sort_by (Optional[str]): The field to sort the sessions by.
|
|
154
|
+
sort_order (Optional[str]): The order to sort the sessions by.
|
|
155
|
+
deserialize (Optional[bool]): Whether to deserialize the sessions.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Union[List[AgentSession], List[TeamSession], List[WorkflowSession], Tuple[List[Dict[str, Any]], int]]:
|
|
159
|
+
- When deserialize=True: List of sessions
|
|
160
|
+
- When deserialize=False: Tuple with list of sessions and total count
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
Exception: If an error occurs while reading the sessions.
|
|
164
|
+
"""
|
|
165
|
+
try:
|
|
166
|
+
# Apply filters
|
|
167
|
+
filtered_sessions = []
|
|
168
|
+
for session_data in self._sessions:
|
|
169
|
+
if user_id is not None and session_data.get("user_id") != user_id:
|
|
170
|
+
continue
|
|
171
|
+
if component_id is not None:
|
|
172
|
+
if session_type == SessionType.AGENT and session_data.get("agent_id") != component_id:
|
|
173
|
+
continue
|
|
174
|
+
elif session_type == SessionType.TEAM and session_data.get("team_id") != component_id:
|
|
175
|
+
continue
|
|
176
|
+
elif session_type == SessionType.WORKFLOW and session_data.get("workflow_id") != component_id:
|
|
177
|
+
continue
|
|
178
|
+
if start_timestamp is not None and session_data.get("created_at", 0) < start_timestamp:
|
|
179
|
+
continue
|
|
180
|
+
if end_timestamp is not None and session_data.get("created_at", 0) > end_timestamp:
|
|
181
|
+
continue
|
|
182
|
+
if session_name is not None:
|
|
183
|
+
stored_name = session_data.get("session_data", {}).get("session_name", "")
|
|
184
|
+
if session_name.lower() not in stored_name.lower():
|
|
185
|
+
continue
|
|
186
|
+
session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
|
|
187
|
+
if session_data.get("session_type") != session_type_value:
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
filtered_sessions.append(session_data)
|
|
191
|
+
|
|
192
|
+
total_count = len(filtered_sessions)
|
|
193
|
+
|
|
194
|
+
# Apply sorting
|
|
195
|
+
filtered_sessions = apply_sorting(filtered_sessions, sort_by, sort_order)
|
|
196
|
+
|
|
197
|
+
# Apply pagination
|
|
198
|
+
if limit is not None:
|
|
199
|
+
start_idx = 0
|
|
200
|
+
if page is not None:
|
|
201
|
+
start_idx = (page - 1) * limit
|
|
202
|
+
filtered_sessions = filtered_sessions[start_idx : start_idx + limit]
|
|
203
|
+
|
|
204
|
+
if not deserialize:
|
|
205
|
+
return filtered_sessions, total_count
|
|
206
|
+
|
|
207
|
+
if session_type == SessionType.AGENT:
|
|
208
|
+
return [AgentSession.from_dict(session) for session in filtered_sessions] # type: ignore
|
|
209
|
+
elif session_type == SessionType.TEAM:
|
|
210
|
+
return [TeamSession.from_dict(session) for session in filtered_sessions] # type: ignore
|
|
211
|
+
elif session_type == SessionType.WORKFLOW:
|
|
212
|
+
return [WorkflowSession.from_dict(session) for session in filtered_sessions] # type: ignore
|
|
213
|
+
else:
|
|
214
|
+
raise ValueError(f"Invalid session type: {session_type}")
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
log_error(f"Exception reading sessions: {e}")
|
|
218
|
+
return [] if deserialize else ([], 0)
|
|
219
|
+
|
|
220
|
+
def rename_session(
|
|
221
|
+
self, session_id: str, session_type: SessionType, session_name: str, deserialize: Optional[bool] = True
|
|
222
|
+
) -> Optional[Union[Session, Dict[str, Any]]]:
|
|
223
|
+
try:
|
|
224
|
+
for i, session in enumerate(self._sessions):
|
|
225
|
+
if session.get("session_id") == session_id and session.get("session_type") == session_type.value:
|
|
226
|
+
# Update session name in session_data
|
|
227
|
+
if "session_data" not in session:
|
|
228
|
+
session["session_data"] = {}
|
|
229
|
+
session["session_data"]["session_name"] = session_name
|
|
230
|
+
|
|
231
|
+
self._sessions[i] = session
|
|
232
|
+
|
|
233
|
+
log_debug(f"Renamed session with id '{session_id}' to '{session_name}'")
|
|
234
|
+
|
|
235
|
+
if not deserialize:
|
|
236
|
+
return session
|
|
237
|
+
|
|
238
|
+
if session_type == SessionType.AGENT:
|
|
239
|
+
return AgentSession.from_dict(session)
|
|
240
|
+
elif session_type == SessionType.TEAM:
|
|
241
|
+
return TeamSession.from_dict(session)
|
|
242
|
+
else:
|
|
243
|
+
return WorkflowSession.from_dict(session)
|
|
244
|
+
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
except Exception as e:
|
|
248
|
+
log_error(f"Exception renaming session: {e}")
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
def upsert_session(
|
|
252
|
+
self, session: Session, deserialize: Optional[bool] = True
|
|
253
|
+
) -> Optional[Union[Session, Dict[str, Any]]]:
|
|
254
|
+
try:
|
|
255
|
+
session_dict = session.to_dict()
|
|
256
|
+
|
|
257
|
+
# Add session_type based on session instance type
|
|
258
|
+
if isinstance(session, AgentSession):
|
|
259
|
+
session_dict["session_type"] = SessionType.AGENT.value
|
|
260
|
+
elif isinstance(session, TeamSession):
|
|
261
|
+
session_dict["session_type"] = SessionType.TEAM.value
|
|
262
|
+
elif isinstance(session, WorkflowSession):
|
|
263
|
+
session_dict["session_type"] = SessionType.WORKFLOW.value
|
|
264
|
+
|
|
265
|
+
# Find existing session to update
|
|
266
|
+
session_updated = False
|
|
267
|
+
for i, existing_session in enumerate(self._sessions):
|
|
268
|
+
if existing_session.get("session_id") == session_dict.get("session_id") and self._matches_session_key(
|
|
269
|
+
existing_session, session
|
|
270
|
+
):
|
|
271
|
+
# Update existing session
|
|
272
|
+
session_dict["updated_at"] = int(time.time())
|
|
273
|
+
self._sessions[i] = session_dict
|
|
274
|
+
session_updated = True
|
|
275
|
+
break
|
|
276
|
+
|
|
277
|
+
if not session_updated:
|
|
278
|
+
# Add new session
|
|
279
|
+
session_dict["created_at"] = session_dict.get("created_at", int(time.time()))
|
|
280
|
+
session_dict["updated_at"] = session_dict.get("created_at")
|
|
281
|
+
self._sessions.append(session_dict)
|
|
282
|
+
|
|
283
|
+
log_debug(f"Upserted session with id '{session.session_id}'")
|
|
284
|
+
|
|
285
|
+
if not deserialize:
|
|
286
|
+
return session_dict
|
|
287
|
+
|
|
288
|
+
return session
|
|
289
|
+
|
|
290
|
+
except Exception as e:
|
|
291
|
+
log_error(f"Exception upserting session: {e}")
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
def _matches_session_key(self, existing_session: Dict[str, Any], session: Session) -> bool:
|
|
295
|
+
"""Check if existing session matches the key for the session type."""
|
|
296
|
+
if isinstance(session, AgentSession):
|
|
297
|
+
return existing_session.get("agent_id") == session.agent_id
|
|
298
|
+
elif isinstance(session, TeamSession):
|
|
299
|
+
return existing_session.get("team_id") == session.team_id
|
|
300
|
+
elif isinstance(session, WorkflowSession):
|
|
301
|
+
return existing_session.get("workflow_id") == session.workflow_id
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
# -- Memory methods --
|
|
305
|
+
def delete_user_memory(self, memory_id: str):
|
|
306
|
+
try:
|
|
307
|
+
original_count = len(self._memories)
|
|
308
|
+
self._memories = [m for m in self._memories if m.get("memory_id") != memory_id]
|
|
309
|
+
|
|
310
|
+
if len(self._memories) < original_count:
|
|
311
|
+
log_debug(f"Successfully deleted user memory id: {memory_id}")
|
|
312
|
+
else:
|
|
313
|
+
log_debug(f"No memory found with id: {memory_id}")
|
|
314
|
+
|
|
315
|
+
except Exception as e:
|
|
316
|
+
log_error(f"Error deleting memory: {e}")
|
|
317
|
+
|
|
318
|
+
def delete_user_memories(self, memory_ids: List[str]) -> None:
|
|
319
|
+
"""Delete multiple user memories from in-memory storage."""
|
|
320
|
+
try:
|
|
321
|
+
self._memories = [m for m in self._memories if m.get("memory_id") not in memory_ids]
|
|
322
|
+
log_debug(f"Successfully deleted {len(memory_ids)} user memories")
|
|
323
|
+
|
|
324
|
+
except Exception as e:
|
|
325
|
+
log_error(f"Error deleting memories: {e}")
|
|
326
|
+
|
|
327
|
+
def get_all_memory_topics(self) -> List[str]:
|
|
328
|
+
try:
|
|
329
|
+
topics = set()
|
|
330
|
+
for memory in self._memories:
|
|
331
|
+
memory_topics = memory.get("topics", [])
|
|
332
|
+
if isinstance(memory_topics, list):
|
|
333
|
+
topics.update(memory_topics)
|
|
334
|
+
return list(topics)
|
|
335
|
+
|
|
336
|
+
except Exception as e:
|
|
337
|
+
log_error(f"Exception reading from memory storage: {e}")
|
|
338
|
+
return []
|
|
339
|
+
|
|
340
|
+
def get_user_memory(
|
|
341
|
+
self, memory_id: str, deserialize: Optional[bool] = True
|
|
342
|
+
) -> Optional[Union[UserMemory, Dict[str, Any]]]:
|
|
343
|
+
try:
|
|
344
|
+
for memory_data in self._memories:
|
|
345
|
+
if memory_data.get("memory_id") == memory_id:
|
|
346
|
+
if not deserialize:
|
|
347
|
+
return memory_data
|
|
348
|
+
return UserMemory.from_dict(memory_data)
|
|
349
|
+
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
except Exception as e:
|
|
353
|
+
log_error(f"Exception reading from memory storage: {e}")
|
|
354
|
+
return None
|
|
355
|
+
|
|
356
|
+
def get_user_memories(
|
|
357
|
+
self,
|
|
358
|
+
user_id: Optional[str] = None,
|
|
359
|
+
agent_id: Optional[str] = None,
|
|
360
|
+
team_id: Optional[str] = None,
|
|
361
|
+
topics: Optional[List[str]] = None,
|
|
362
|
+
search_content: Optional[str] = None,
|
|
363
|
+
limit: Optional[int] = None,
|
|
364
|
+
page: Optional[int] = None,
|
|
365
|
+
sort_by: Optional[str] = None,
|
|
366
|
+
sort_order: Optional[str] = None,
|
|
367
|
+
deserialize: Optional[bool] = True,
|
|
368
|
+
) -> Union[List[UserMemory], Tuple[List[Dict[str, Any]], int]]:
|
|
369
|
+
try:
|
|
370
|
+
# Apply filters
|
|
371
|
+
filtered_memories = []
|
|
372
|
+
for memory_data in self._memories:
|
|
373
|
+
if user_id is not None and memory_data.get("user_id") != user_id:
|
|
374
|
+
continue
|
|
375
|
+
if agent_id is not None and memory_data.get("agent_id") != agent_id:
|
|
376
|
+
continue
|
|
377
|
+
if team_id is not None and memory_data.get("team_id") != team_id:
|
|
378
|
+
continue
|
|
379
|
+
if topics is not None:
|
|
380
|
+
memory_topics = memory_data.get("topics", [])
|
|
381
|
+
if not any(topic in memory_topics for topic in topics):
|
|
382
|
+
continue
|
|
383
|
+
if search_content is not None:
|
|
384
|
+
memory_content = str(memory_data.get("memory", ""))
|
|
385
|
+
if search_content.lower() not in memory_content.lower():
|
|
386
|
+
continue
|
|
387
|
+
|
|
388
|
+
filtered_memories.append(memory_data)
|
|
389
|
+
|
|
390
|
+
total_count = len(filtered_memories)
|
|
391
|
+
|
|
392
|
+
# Apply sorting
|
|
393
|
+
filtered_memories = apply_sorting(filtered_memories, sort_by, sort_order)
|
|
394
|
+
|
|
395
|
+
# Apply pagination
|
|
396
|
+
if limit is not None:
|
|
397
|
+
start_idx = 0
|
|
398
|
+
if page is not None:
|
|
399
|
+
start_idx = (page - 1) * limit
|
|
400
|
+
filtered_memories = filtered_memories[start_idx : start_idx + limit]
|
|
401
|
+
|
|
402
|
+
if not deserialize:
|
|
403
|
+
return filtered_memories, total_count
|
|
404
|
+
|
|
405
|
+
return [UserMemory.from_dict(memory) for memory in filtered_memories]
|
|
406
|
+
|
|
407
|
+
except Exception as e:
|
|
408
|
+
log_error(f"Exception reading from memory storage: {e}")
|
|
409
|
+
return [] if deserialize else ([], 0)
|
|
410
|
+
|
|
411
|
+
def get_user_memory_stats(
|
|
412
|
+
self, limit: Optional[int] = None, page: Optional[int] = None
|
|
413
|
+
) -> Tuple[List[Dict[str, Any]], int]:
|
|
414
|
+
"""Get user memory statistics."""
|
|
415
|
+
try:
|
|
416
|
+
user_stats = {}
|
|
417
|
+
|
|
418
|
+
for memory in self._memories:
|
|
419
|
+
user_id = memory.get("user_id")
|
|
420
|
+
if user_id:
|
|
421
|
+
if user_id not in user_stats:
|
|
422
|
+
user_stats[user_id] = {"user_id": user_id, "total_memories": 0, "last_memory_updated_at": 0}
|
|
423
|
+
user_stats[user_id]["total_memories"] += 1
|
|
424
|
+
updated_at = memory.get("updated_at", 0)
|
|
425
|
+
if updated_at > user_stats[user_id]["last_memory_updated_at"]:
|
|
426
|
+
user_stats[user_id]["last_memory_updated_at"] = updated_at
|
|
427
|
+
|
|
428
|
+
stats_list = list(user_stats.values())
|
|
429
|
+
stats_list.sort(key=lambda x: x["last_memory_updated_at"], reverse=True)
|
|
430
|
+
|
|
431
|
+
total_count = len(stats_list)
|
|
432
|
+
|
|
433
|
+
# Apply pagination
|
|
434
|
+
if limit is not None:
|
|
435
|
+
start_idx = 0
|
|
436
|
+
if page is not None:
|
|
437
|
+
start_idx = (page - 1) * limit
|
|
438
|
+
stats_list = stats_list[start_idx : start_idx + limit]
|
|
439
|
+
|
|
440
|
+
return stats_list, total_count
|
|
441
|
+
|
|
442
|
+
except Exception as e:
|
|
443
|
+
log_error(f"Exception getting user memory stats: {e}")
|
|
444
|
+
return [], 0
|
|
445
|
+
|
|
446
|
+
def upsert_user_memory(
|
|
447
|
+
self, memory: UserMemory, deserialize: Optional[bool] = True
|
|
448
|
+
) -> Optional[Union[UserMemory, Dict[str, Any]]]:
|
|
449
|
+
try:
|
|
450
|
+
if memory.memory_id is None:
|
|
451
|
+
memory.memory_id = str(uuid4())
|
|
452
|
+
|
|
453
|
+
memory_dict = memory.to_dict() if hasattr(memory, "to_dict") else memory.__dict__
|
|
454
|
+
memory_dict["updated_at"] = int(time.time())
|
|
455
|
+
|
|
456
|
+
# Find existing memory to update
|
|
457
|
+
memory_updated = False
|
|
458
|
+
for i, existing_memory in enumerate(self._memories):
|
|
459
|
+
if existing_memory.get("memory_id") == memory.memory_id:
|
|
460
|
+
self._memories[i] = memory_dict
|
|
461
|
+
memory_updated = True
|
|
462
|
+
break
|
|
463
|
+
|
|
464
|
+
if not memory_updated:
|
|
465
|
+
self._memories.append(memory_dict)
|
|
466
|
+
|
|
467
|
+
log_debug(f"Upserted user memory with id '{memory.memory_id}'")
|
|
468
|
+
|
|
469
|
+
if not deserialize:
|
|
470
|
+
return memory_dict
|
|
471
|
+
return UserMemory.from_dict(memory_dict)
|
|
472
|
+
|
|
473
|
+
except Exception as e:
|
|
474
|
+
log_warning(f"Exception upserting user memory: {e}")
|
|
475
|
+
return None
|
|
476
|
+
|
|
477
|
+
def clear_memories(self) -> None:
|
|
478
|
+
"""Delete all memories.
|
|
479
|
+
|
|
480
|
+
Raises:
|
|
481
|
+
Exception: If an error occurs during deletion.
|
|
482
|
+
"""
|
|
483
|
+
try:
|
|
484
|
+
self._memories.clear()
|
|
485
|
+
|
|
486
|
+
except Exception as e:
|
|
487
|
+
log_warning(f"Exception deleting all memories: {e}")
|
|
488
|
+
|
|
489
|
+
# -- Metrics methods --
|
|
490
|
+
def calculate_metrics(self) -> Optional[list[dict]]:
|
|
491
|
+
"""Calculate metrics for all dates without complete metrics."""
|
|
492
|
+
try:
|
|
493
|
+
starting_date = self._get_metrics_calculation_starting_date(self._metrics)
|
|
494
|
+
if starting_date is None:
|
|
495
|
+
log_info("No session data found. Won't calculate metrics.")
|
|
496
|
+
return None
|
|
497
|
+
|
|
498
|
+
dates_to_process = get_dates_to_calculate_metrics_for(starting_date)
|
|
499
|
+
if not dates_to_process:
|
|
500
|
+
log_info("Metrics already calculated for all relevant dates.")
|
|
501
|
+
return None
|
|
502
|
+
|
|
503
|
+
start_timestamp = int(datetime.combine(dates_to_process[0], datetime.min.time()).timestamp())
|
|
504
|
+
end_timestamp = int(
|
|
505
|
+
datetime.combine(dates_to_process[-1] + timedelta(days=1), datetime.min.time()).timestamp()
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
sessions = self._get_all_sessions_for_metrics_calculation(start_timestamp, end_timestamp)
|
|
509
|
+
all_sessions_data = fetch_all_sessions_data(
|
|
510
|
+
sessions=sessions, dates_to_process=dates_to_process, start_timestamp=start_timestamp
|
|
511
|
+
)
|
|
512
|
+
if not all_sessions_data:
|
|
513
|
+
log_info("No new session data found. Won't calculate metrics.")
|
|
514
|
+
return None
|
|
515
|
+
|
|
516
|
+
results = []
|
|
517
|
+
|
|
518
|
+
for date_to_process in dates_to_process:
|
|
519
|
+
date_key = date_to_process.isoformat()
|
|
520
|
+
sessions_for_date = all_sessions_data.get(date_key, {})
|
|
521
|
+
|
|
522
|
+
# Skip dates with no sessions
|
|
523
|
+
if not any(len(sessions) > 0 for sessions in sessions_for_date.values()):
|
|
524
|
+
continue
|
|
525
|
+
|
|
526
|
+
metrics_record = calculate_date_metrics(date_to_process, sessions_for_date)
|
|
527
|
+
|
|
528
|
+
# Upsert metrics record
|
|
529
|
+
existing_record_idx = None
|
|
530
|
+
for i, existing_metric in enumerate(self._metrics):
|
|
531
|
+
if (
|
|
532
|
+
existing_metric.get("date") == str(date_to_process)
|
|
533
|
+
and existing_metric.get("aggregation_period") == "daily"
|
|
534
|
+
):
|
|
535
|
+
existing_record_idx = i
|
|
536
|
+
break
|
|
537
|
+
|
|
538
|
+
if existing_record_idx is not None:
|
|
539
|
+
self._metrics[existing_record_idx] = metrics_record
|
|
540
|
+
else:
|
|
541
|
+
self._metrics.append(metrics_record)
|
|
542
|
+
|
|
543
|
+
results.append(metrics_record)
|
|
544
|
+
|
|
545
|
+
log_debug("Updated metrics calculations")
|
|
546
|
+
|
|
547
|
+
return results
|
|
548
|
+
|
|
549
|
+
except Exception as e:
|
|
550
|
+
log_warning(f"Exception refreshing metrics: {e}")
|
|
551
|
+
return None
|
|
552
|
+
|
|
553
|
+
def _get_metrics_calculation_starting_date(self, metrics: List[Dict[str, Any]]) -> Optional[date]:
|
|
554
|
+
"""Get the first date for which metrics calculation is needed."""
|
|
555
|
+
if metrics:
|
|
556
|
+
# Sort by date in descending order
|
|
557
|
+
sorted_metrics = sorted(metrics, key=lambda x: x.get("date", ""), reverse=True)
|
|
558
|
+
latest_metric = sorted_metrics[0]
|
|
559
|
+
|
|
560
|
+
if latest_metric.get("completed", False):
|
|
561
|
+
latest_date = datetime.strptime(latest_metric["date"], "%Y-%m-%d").date()
|
|
562
|
+
return latest_date + timedelta(days=1)
|
|
563
|
+
else:
|
|
564
|
+
return datetime.strptime(latest_metric["date"], "%Y-%m-%d").date()
|
|
565
|
+
|
|
566
|
+
# No metrics records. Return the date of the first recorded session.
|
|
567
|
+
if self._sessions:
|
|
568
|
+
# Sort by created_at
|
|
569
|
+
sorted_sessions = sorted(self._sessions, key=lambda x: x.get("created_at", 0))
|
|
570
|
+
first_session_date = sorted_sessions[0]["created_at"]
|
|
571
|
+
return datetime.fromtimestamp(first_session_date, tz=timezone.utc).date()
|
|
572
|
+
|
|
573
|
+
return None
|
|
574
|
+
|
|
575
|
+
def _get_all_sessions_for_metrics_calculation(
|
|
576
|
+
self, start_timestamp: Optional[int] = None, end_timestamp: Optional[int] = None
|
|
577
|
+
) -> List[Dict[str, Any]]:
|
|
578
|
+
"""Get all sessions for metrics calculation."""
|
|
579
|
+
try:
|
|
580
|
+
filtered_sessions = []
|
|
581
|
+
for session in self._sessions:
|
|
582
|
+
created_at = session.get("created_at", 0)
|
|
583
|
+
if start_timestamp is not None and created_at < start_timestamp:
|
|
584
|
+
continue
|
|
585
|
+
if end_timestamp is not None and created_at >= end_timestamp:
|
|
586
|
+
continue
|
|
587
|
+
|
|
588
|
+
# Only include necessary fields for metrics
|
|
589
|
+
filtered_session = {
|
|
590
|
+
"user_id": session.get("user_id"),
|
|
591
|
+
"session_data": session.get("session_data"),
|
|
592
|
+
"runs": session.get("runs"),
|
|
593
|
+
"created_at": session.get("created_at"),
|
|
594
|
+
"session_type": session.get("session_type"),
|
|
595
|
+
}
|
|
596
|
+
filtered_sessions.append(filtered_session)
|
|
597
|
+
|
|
598
|
+
return filtered_sessions
|
|
599
|
+
|
|
600
|
+
except Exception as e:
|
|
601
|
+
log_error(f"Exception reading sessions for metrics: {e}")
|
|
602
|
+
return []
|
|
603
|
+
|
|
604
|
+
def get_metrics(
|
|
605
|
+
self,
|
|
606
|
+
starting_date: Optional[date] = None,
|
|
607
|
+
ending_date: Optional[date] = None,
|
|
608
|
+
) -> Tuple[List[dict], Optional[int]]:
|
|
609
|
+
"""Get all metrics matching the given date range."""
|
|
610
|
+
try:
|
|
611
|
+
filtered_metrics = []
|
|
612
|
+
latest_updated_at = None
|
|
613
|
+
|
|
614
|
+
for metric in self._metrics:
|
|
615
|
+
metric_date = datetime.strptime(metric.get("date", ""), "%Y-%m-%d").date()
|
|
616
|
+
|
|
617
|
+
if starting_date and metric_date < starting_date:
|
|
618
|
+
continue
|
|
619
|
+
if ending_date and metric_date > ending_date:
|
|
620
|
+
continue
|
|
621
|
+
|
|
622
|
+
filtered_metrics.append(metric)
|
|
623
|
+
|
|
624
|
+
updated_at = metric.get("updated_at")
|
|
625
|
+
if updated_at and (latest_updated_at is None or updated_at > latest_updated_at):
|
|
626
|
+
latest_updated_at = updated_at
|
|
627
|
+
|
|
628
|
+
return filtered_metrics, latest_updated_at
|
|
629
|
+
|
|
630
|
+
except Exception as e:
|
|
631
|
+
log_error(f"Exception getting metrics: {e}")
|
|
632
|
+
return [], None
|
|
633
|
+
|
|
634
|
+
# -- Knowledge methods --
|
|
635
|
+
|
|
636
|
+
def delete_knowledge_content(self, id: str):
|
|
637
|
+
"""Delete a knowledge row from in-memory storage.
|
|
638
|
+
|
|
639
|
+
Args:
|
|
640
|
+
id (str): The ID of the knowledge row to delete.
|
|
641
|
+
|
|
642
|
+
Raises:
|
|
643
|
+
Exception: If an error occurs during deletion.
|
|
644
|
+
"""
|
|
645
|
+
try:
|
|
646
|
+
self._knowledge = [item for item in self._knowledge if item.get("id") != id]
|
|
647
|
+
|
|
648
|
+
except Exception as e:
|
|
649
|
+
log_error(f"Error deleting knowledge content: {e}")
|
|
650
|
+
|
|
651
|
+
def get_knowledge_content(self, id: str) -> Optional[KnowledgeRow]:
|
|
652
|
+
"""Get a knowledge row from in-memory storage.
|
|
653
|
+
|
|
654
|
+
Args:
|
|
655
|
+
id (str): The ID of the knowledge row to get.
|
|
656
|
+
|
|
657
|
+
Returns:
|
|
658
|
+
Optional[KnowledgeRow]: The knowledge row, or None if it doesn't exist.
|
|
659
|
+
|
|
660
|
+
Raises:
|
|
661
|
+
Exception: If an error occurs during retrieval.
|
|
662
|
+
"""
|
|
663
|
+
try:
|
|
664
|
+
for item in self._knowledge:
|
|
665
|
+
if item.get("id") == id:
|
|
666
|
+
return KnowledgeRow.model_validate(item)
|
|
667
|
+
|
|
668
|
+
return None
|
|
669
|
+
|
|
670
|
+
except Exception as e:
|
|
671
|
+
log_error(f"Error getting knowledge content: {e}")
|
|
672
|
+
return None
|
|
673
|
+
|
|
674
|
+
def get_knowledge_contents(
|
|
675
|
+
self,
|
|
676
|
+
limit: Optional[int] = None,
|
|
677
|
+
page: Optional[int] = None,
|
|
678
|
+
sort_by: Optional[str] = None,
|
|
679
|
+
sort_order: Optional[str] = None,
|
|
680
|
+
) -> Tuple[List[KnowledgeRow], int]:
|
|
681
|
+
"""Get all knowledge contents from in-memory storage.
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
limit (Optional[int]): The maximum number of knowledge contents to return.
|
|
685
|
+
page (Optional[int]): The page number.
|
|
686
|
+
sort_by (Optional[str]): The column to sort by.
|
|
687
|
+
sort_order (Optional[str]): The order to sort by.
|
|
688
|
+
|
|
689
|
+
Returns:
|
|
690
|
+
Tuple[List[KnowledgeRow], int]: The knowledge contents and total count.
|
|
691
|
+
|
|
692
|
+
Raises:
|
|
693
|
+
Exception: If an error occurs during retrieval.
|
|
694
|
+
"""
|
|
695
|
+
try:
|
|
696
|
+
knowledge_items = self._knowledge.copy()
|
|
697
|
+
|
|
698
|
+
total_count = len(knowledge_items)
|
|
699
|
+
|
|
700
|
+
# Apply sorting
|
|
701
|
+
knowledge_items = apply_sorting(knowledge_items, sort_by, sort_order)
|
|
702
|
+
|
|
703
|
+
# Apply pagination
|
|
704
|
+
if limit is not None:
|
|
705
|
+
start_idx = 0
|
|
706
|
+
if page is not None:
|
|
707
|
+
start_idx = (page - 1) * limit
|
|
708
|
+
knowledge_items = knowledge_items[start_idx : start_idx + limit]
|
|
709
|
+
|
|
710
|
+
return [KnowledgeRow.model_validate(item) for item in knowledge_items], total_count
|
|
711
|
+
|
|
712
|
+
except Exception as e:
|
|
713
|
+
log_error(f"Error getting knowledge contents: {e}")
|
|
714
|
+
return [], 0
|
|
715
|
+
|
|
716
|
+
def upsert_knowledge_content(self, knowledge_row: KnowledgeRow):
|
|
717
|
+
"""Upsert knowledge content.
|
|
718
|
+
|
|
719
|
+
Args:
|
|
720
|
+
knowledge_row (KnowledgeRow): The knowledge row to upsert.
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
Optional[KnowledgeRow]: The upserted knowledge row, or None if the operation fails.
|
|
724
|
+
|
|
725
|
+
Raises:
|
|
726
|
+
Exception: If an error occurs during upsert.
|
|
727
|
+
"""
|
|
728
|
+
try:
|
|
729
|
+
knowledge_dict = knowledge_row.model_dump()
|
|
730
|
+
|
|
731
|
+
# Find existing item to update
|
|
732
|
+
item_updated = False
|
|
733
|
+
for i, existing_item in enumerate(self._knowledge):
|
|
734
|
+
if existing_item.get("id") == knowledge_row.id:
|
|
735
|
+
self._knowledge[i] = knowledge_dict
|
|
736
|
+
item_updated = True
|
|
737
|
+
break
|
|
738
|
+
|
|
739
|
+
if not item_updated:
|
|
740
|
+
self._knowledge.append(knowledge_dict)
|
|
741
|
+
|
|
742
|
+
log_debug(f"Upserted knowledge source with id '{knowledge_row.id}'")
|
|
743
|
+
|
|
744
|
+
return knowledge_row
|
|
745
|
+
|
|
746
|
+
except Exception as e:
|
|
747
|
+
log_error(f"Error upserting knowledge row: {e}")
|
|
748
|
+
return None
|
|
749
|
+
|
|
750
|
+
# -- Eval methods --
|
|
751
|
+
|
|
752
|
+
def create_eval_run(self, eval_run: EvalRunRecord) -> Optional[EvalRunRecord]:
|
|
753
|
+
"""Create an EvalRunRecord"""
|
|
754
|
+
try:
|
|
755
|
+
current_time = int(time.time())
|
|
756
|
+
eval_dict = eval_run.model_dump()
|
|
757
|
+
eval_dict["created_at"] = current_time
|
|
758
|
+
eval_dict["updated_at"] = current_time
|
|
759
|
+
|
|
760
|
+
self._eval_runs.append(eval_dict)
|
|
761
|
+
|
|
762
|
+
log_debug(f"Created eval run with id '{eval_run.run_id}'")
|
|
763
|
+
|
|
764
|
+
return eval_run
|
|
765
|
+
|
|
766
|
+
except Exception as e:
|
|
767
|
+
log_error(f"Error creating eval run: {e}")
|
|
768
|
+
return None
|
|
769
|
+
|
|
770
|
+
def delete_eval_runs(self, eval_run_ids: List[str]) -> None:
|
|
771
|
+
"""Delete multiple eval runs from in-memory storage."""
|
|
772
|
+
try:
|
|
773
|
+
original_count = len(self._eval_runs)
|
|
774
|
+
self._eval_runs = [run for run in self._eval_runs if run.get("run_id") not in eval_run_ids]
|
|
775
|
+
|
|
776
|
+
deleted_count = original_count - len(self._eval_runs)
|
|
777
|
+
if deleted_count > 0:
|
|
778
|
+
log_debug(f"Deleted {deleted_count} eval runs")
|
|
779
|
+
else:
|
|
780
|
+
log_debug(f"No eval runs found with IDs: {eval_run_ids}")
|
|
781
|
+
|
|
782
|
+
except Exception as e:
|
|
783
|
+
log_error(f"Error deleting eval runs {eval_run_ids}: {e}")
|
|
784
|
+
|
|
785
|
+
def get_eval_run(
|
|
786
|
+
self, eval_run_id: str, deserialize: Optional[bool] = True
|
|
787
|
+
) -> Optional[Union[EvalRunRecord, Dict[str, Any]]]:
|
|
788
|
+
"""Get an eval run from in-memory storage."""
|
|
789
|
+
try:
|
|
790
|
+
for run_data in self._eval_runs:
|
|
791
|
+
if run_data.get("run_id") == eval_run_id:
|
|
792
|
+
if not deserialize:
|
|
793
|
+
return run_data
|
|
794
|
+
return EvalRunRecord.model_validate(run_data)
|
|
795
|
+
|
|
796
|
+
return None
|
|
797
|
+
|
|
798
|
+
except Exception as e:
|
|
799
|
+
log_error(f"Exception getting eval run {eval_run_id}: {e}")
|
|
800
|
+
return None
|
|
801
|
+
|
|
802
|
+
def get_eval_runs(
|
|
803
|
+
self,
|
|
804
|
+
limit: Optional[int] = None,
|
|
805
|
+
page: Optional[int] = None,
|
|
806
|
+
sort_by: Optional[str] = None,
|
|
807
|
+
sort_order: Optional[str] = None,
|
|
808
|
+
agent_id: Optional[str] = None,
|
|
809
|
+
team_id: Optional[str] = None,
|
|
810
|
+
workflow_id: Optional[str] = None,
|
|
811
|
+
model_id: Optional[str] = None,
|
|
812
|
+
filter_type: Optional[EvalFilterType] = None,
|
|
813
|
+
eval_type: Optional[List[EvalType]] = None,
|
|
814
|
+
deserialize: Optional[bool] = True,
|
|
815
|
+
) -> Union[List[EvalRunRecord], Tuple[List[Dict[str, Any]], int]]:
|
|
816
|
+
"""Get all eval runs from in-memory storage with filtering and pagination."""
|
|
817
|
+
try:
|
|
818
|
+
# Apply filters
|
|
819
|
+
filtered_runs = []
|
|
820
|
+
for run_data in self._eval_runs:
|
|
821
|
+
if agent_id is not None and run_data.get("agent_id") != agent_id:
|
|
822
|
+
continue
|
|
823
|
+
if team_id is not None and run_data.get("team_id") != team_id:
|
|
824
|
+
continue
|
|
825
|
+
if workflow_id is not None and run_data.get("workflow_id") != workflow_id:
|
|
826
|
+
continue
|
|
827
|
+
if model_id is not None and run_data.get("model_id") != model_id:
|
|
828
|
+
continue
|
|
829
|
+
if eval_type is not None and len(eval_type) > 0:
|
|
830
|
+
if run_data.get("eval_type") not in eval_type:
|
|
831
|
+
continue
|
|
832
|
+
if filter_type is not None:
|
|
833
|
+
if filter_type == EvalFilterType.AGENT and run_data.get("agent_id") is None:
|
|
834
|
+
continue
|
|
835
|
+
elif filter_type == EvalFilterType.TEAM and run_data.get("team_id") is None:
|
|
836
|
+
continue
|
|
837
|
+
elif filter_type == EvalFilterType.WORKFLOW and run_data.get("workflow_id") is None:
|
|
838
|
+
continue
|
|
839
|
+
|
|
840
|
+
filtered_runs.append(run_data)
|
|
841
|
+
|
|
842
|
+
total_count = len(filtered_runs)
|
|
843
|
+
|
|
844
|
+
# Apply sorting (default by created_at desc)
|
|
845
|
+
if sort_by is None:
|
|
846
|
+
filtered_runs.sort(key=lambda x: x.get("created_at", 0), reverse=True)
|
|
847
|
+
else:
|
|
848
|
+
filtered_runs = apply_sorting(filtered_runs, sort_by, sort_order)
|
|
849
|
+
|
|
850
|
+
# Apply pagination
|
|
851
|
+
if limit is not None:
|
|
852
|
+
start_idx = 0
|
|
853
|
+
if page is not None:
|
|
854
|
+
start_idx = (page - 1) * limit
|
|
855
|
+
filtered_runs = filtered_runs[start_idx : start_idx + limit]
|
|
856
|
+
|
|
857
|
+
if not deserialize:
|
|
858
|
+
return filtered_runs, total_count
|
|
859
|
+
|
|
860
|
+
return [EvalRunRecord.model_validate(run) for run in filtered_runs]
|
|
861
|
+
|
|
862
|
+
except Exception as e:
|
|
863
|
+
log_error(f"Exception getting eval runs: {e}")
|
|
864
|
+
return [] if deserialize else ([], 0)
|
|
865
|
+
|
|
866
|
+
def rename_eval_run(
|
|
867
|
+
self, eval_run_id: str, name: str, deserialize: Optional[bool] = True
|
|
868
|
+
) -> Optional[Union[EvalRunRecord, Dict[str, Any]]]:
|
|
869
|
+
"""Rename an eval run."""
|
|
870
|
+
try:
|
|
871
|
+
for i, run_data in enumerate(self._eval_runs):
|
|
872
|
+
if run_data.get("run_id") == eval_run_id:
|
|
873
|
+
run_data["name"] = name
|
|
874
|
+
run_data["updated_at"] = int(time.time())
|
|
875
|
+
self._eval_runs[i] = run_data
|
|
876
|
+
|
|
877
|
+
log_debug(f"Renamed eval run with id '{eval_run_id}' to '{name}'")
|
|
878
|
+
|
|
879
|
+
if not deserialize:
|
|
880
|
+
return run_data
|
|
881
|
+
|
|
882
|
+
return EvalRunRecord.model_validate(run_data)
|
|
883
|
+
|
|
884
|
+
return None
|
|
885
|
+
|
|
886
|
+
except Exception as e:
|
|
887
|
+
log_error(f"Error renaming eval run {eval_run_id}: {e}")
|
|
888
|
+
return None
|