agno 2.1.2__py3-none-any.whl → 2.3.13__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/agent/agent.py +5540 -2273
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +689 -6
- agno/db/dynamo/dynamo.py +933 -37
- agno/db/dynamo/schemas.py +174 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +831 -9
- agno/db/firestore/schemas.py +51 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +660 -12
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +287 -14
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +590 -14
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +43 -13
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2760 -0
- agno/db/mongo/mongo.py +879 -11
- agno/db/mongo/schemas.py +42 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +946 -68
- agno/db/mysql/schemas.py +72 -10
- agno/db/mysql/utils.py +198 -7
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +942 -57
- agno/db/postgres/schemas.py +81 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +671 -7
- agno/db/redis/schemas.py +50 -0
- agno/db/redis/utils.py +65 -7
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +17 -2
- agno/db/singlestore/schemas.py +63 -0
- agno/db/singlestore/singlestore.py +949 -83
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +62 -0
- agno/db/sqlite/sqlite.py +965 -46
- agno/db/sqlite/utils.py +169 -8
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +2 -0
- agno/eval/__init__.py +10 -0
- agno/eval/accuracy.py +75 -55
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +16 -7
- agno/eval/reliability.py +28 -16
- agno/eval/utils.py +35 -17
- agno/exceptions.py +27 -2
- agno/filters.py +354 -0
- agno/guardrails/prompt_injection.py +1 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +1 -1
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/semantic.py +9 -4
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +8 -0
- agno/knowledge/embedder/openai.py +8 -8
- agno/knowledge/embedder/sentence_transformer.py +6 -2
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/knowledge.py +1618 -318
- agno/knowledge/reader/base.py +6 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +17 -19
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +32 -3
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/tavily_reader.py +193 -0
- agno/knowledge/reader/text_reader.py +22 -10
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/knowledge/reader/wikipedia_reader.py +33 -1
- agno/knowledge/types.py +1 -0
- agno/knowledge/utils.py +72 -7
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +544 -83
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +515 -40
- agno/models/aws/bedrock.py +102 -21
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +41 -19
- agno/models/azure/openai_chat.py +39 -8
- agno/models/base.py +1249 -525
- agno/models/cerebras/cerebras.py +91 -21
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +40 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +877 -80
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +51 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +44 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +28 -5
- agno/models/meta/llama.py +47 -14
- agno/models/meta/llama_openai.py +22 -17
- agno/models/mistral/mistral.py +8 -4
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/chat.py +24 -8
- agno/models/openai/chat.py +104 -29
- agno/models/openai/responses.py +101 -81
- agno/models/openrouter/openrouter.py +60 -3
- agno/models/perplexity/perplexity.py +17 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +24 -4
- agno/models/response.py +73 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +549 -152
- agno/os/auth.py +190 -3
- agno/os/config.py +23 -0
- agno/os/interfaces/a2a/router.py +8 -11
- agno/os/interfaces/a2a/utils.py +1 -1
- agno/os/interfaces/agui/router.py +18 -3
- agno/os/interfaces/agui/utils.py +152 -39
- agno/os/interfaces/slack/router.py +55 -37
- agno/os/interfaces/slack/slack.py +9 -1
- agno/os/interfaces/whatsapp/router.py +0 -1
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/mcp.py +110 -52
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/jwt.py +676 -112
- agno/os/router.py +40 -1478
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/evals.py +96 -39
- agno/os/routers/evals/schemas.py +65 -33
- agno/os/routers/evals/utils.py +80 -10
- agno/os/routers/health.py +10 -4
- agno/os/routers/knowledge/knowledge.py +196 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +279 -52
- agno/os/routers/memory/schemas.py +46 -17
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +462 -34
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +256 -693
- agno/os/scopes.py +469 -0
- agno/os/utils.py +514 -36
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +155 -32
- agno/run/base.py +55 -3
- agno/run/requirement.py +181 -0
- agno/run/team.py +125 -38
- agno/run/workflow.py +72 -18
- agno/session/agent.py +102 -89
- agno/session/summary.py +56 -15
- agno/session/team.py +164 -90
- agno/session/workflow.py +405 -40
- agno/table.py +10 -0
- agno/team/team.py +3974 -1903
- agno/tools/dalle.py +2 -4
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +16 -10
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +193 -38
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +271 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +3 -3
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +76 -36
- agno/tools/redshift.py +406 -0
- agno/tools/scrapegraph.py +1 -1
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +18 -3
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +146 -0
- agno/tools/toolkit.py +25 -0
- agno/tools/workflow.py +8 -1
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +151 -3
- agno/utils/gemini.py +15 -5
- agno/utils/hooks.py +118 -4
- agno/utils/http.py +113 -2
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +187 -1
- agno/utils/merge_dict.py +3 -3
- agno/utils/message.py +60 -0
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +49 -14
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/print_response/agent.py +109 -16
- agno/utils/print_response/team.py +223 -30
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/streamlit.py +1 -1
- agno/utils/team.py +98 -9
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +39 -7
- agno/vectordb/cassandra/cassandra.py +21 -5
- agno/vectordb/chroma/chromadb.py +43 -12
- agno/vectordb/clickhouse/clickhousedb.py +21 -5
- agno/vectordb/couchbase/couchbase.py +29 -5
- agno/vectordb/lancedb/lance_db.py +92 -181
- agno/vectordb/langchaindb/langchaindb.py +24 -4
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/llamaindexdb.py +25 -5
- agno/vectordb/milvus/milvus.py +50 -37
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +36 -30
- agno/vectordb/pgvector/pgvector.py +201 -77
- agno/vectordb/pineconedb/pineconedb.py +41 -23
- agno/vectordb/qdrant/qdrant.py +67 -54
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/singlestore.py +50 -29
- agno/vectordb/surrealdb/surrealdb.py +31 -41
- agno/vectordb/upstashdb/upstashdb.py +34 -6
- agno/vectordb/weaviate/weaviate.py +53 -14
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +120 -18
- agno/workflow/loop.py +77 -10
- agno/workflow/parallel.py +231 -143
- agno/workflow/router.py +118 -17
- agno/workflow/step.py +609 -170
- agno/workflow/steps.py +73 -6
- agno/workflow/types.py +96 -21
- agno/workflow/workflow.py +2039 -262
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
- agno-2.3.13.dist-info/RECORD +613 -0
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -679
- agno/tools/memori.py +0 -339
- agno-2.1.2.dist-info/RECORD +0 -543
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/os/routers/evals/utils.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Optional, Union
|
|
2
2
|
|
|
3
3
|
from fastapi import HTTPException
|
|
4
4
|
|
|
5
5
|
from agno.agent.agent import Agent
|
|
6
|
-
from agno.db.base import BaseDb
|
|
6
|
+
from agno.db.base import AsyncBaseDb, BaseDb
|
|
7
7
|
from agno.eval.accuracy import AccuracyEval
|
|
8
|
+
from agno.eval.agent_as_judge import AgentAsJudgeEval
|
|
8
9
|
from agno.eval.performance import PerformanceEval
|
|
9
10
|
from agno.eval.reliability import ReliabilityEval
|
|
10
11
|
from agno.models.base import Model
|
|
@@ -14,7 +15,7 @@ from agno.team.team import Team
|
|
|
14
15
|
|
|
15
16
|
async def run_accuracy_eval(
|
|
16
17
|
eval_run_input: EvalRunInput,
|
|
17
|
-
db: BaseDb,
|
|
18
|
+
db: Union[BaseDb, AsyncBaseDb],
|
|
18
19
|
agent: Optional[Agent] = None,
|
|
19
20
|
team: Optional[Team] = None,
|
|
20
21
|
default_model: Optional[Model] = None,
|
|
@@ -33,6 +34,7 @@ async def run_accuracy_eval(
|
|
|
33
34
|
additional_context=eval_run_input.additional_context,
|
|
34
35
|
num_iterations=eval_run_input.num_iterations or 1,
|
|
35
36
|
name=eval_run_input.name,
|
|
37
|
+
model=default_model,
|
|
36
38
|
)
|
|
37
39
|
|
|
38
40
|
result = await accuracy_eval.arun(print_results=False, print_summary=False)
|
|
@@ -41,6 +43,71 @@ async def run_accuracy_eval(
|
|
|
41
43
|
|
|
42
44
|
eval_run = EvalSchema.from_accuracy_eval(accuracy_eval=accuracy_eval, result=result)
|
|
43
45
|
|
|
46
|
+
# Restore original model after eval
|
|
47
|
+
if default_model is not None:
|
|
48
|
+
if agent is not None:
|
|
49
|
+
agent.model = default_model
|
|
50
|
+
elif team is not None:
|
|
51
|
+
team.model = default_model
|
|
52
|
+
|
|
53
|
+
return eval_run
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def run_agent_as_judge_eval(
|
|
57
|
+
eval_run_input: EvalRunInput,
|
|
58
|
+
db: Union[BaseDb, AsyncBaseDb],
|
|
59
|
+
agent: Optional[Agent] = None,
|
|
60
|
+
team: Optional[Team] = None,
|
|
61
|
+
default_model: Optional[Model] = None,
|
|
62
|
+
) -> EvalSchema:
|
|
63
|
+
"""Run an AgentAsJudge evaluation for the given agent or team"""
|
|
64
|
+
if not eval_run_input.criteria:
|
|
65
|
+
raise HTTPException(status_code=400, detail="criteria is required for agent-as-judge evaluation")
|
|
66
|
+
|
|
67
|
+
# Run agent/team to get output
|
|
68
|
+
if agent:
|
|
69
|
+
agent_response = await agent.arun(eval_run_input.input, stream=False)
|
|
70
|
+
output = str(agent_response.content) if agent_response.content else ""
|
|
71
|
+
model_id = agent.model.id if agent and agent.model else None
|
|
72
|
+
model_provider = agent.model.provider if agent and agent.model else None
|
|
73
|
+
agent_id = agent.id
|
|
74
|
+
team_id = None
|
|
75
|
+
elif team:
|
|
76
|
+
team_response = await team.arun(eval_run_input.input, stream=False)
|
|
77
|
+
output = str(team_response.content) if team_response.content else ""
|
|
78
|
+
model_id = team.model.id if team and team.model else None
|
|
79
|
+
model_provider = team.model.provider if team and team.model else None
|
|
80
|
+
agent_id = None
|
|
81
|
+
team_id = team.id
|
|
82
|
+
else:
|
|
83
|
+
raise HTTPException(status_code=400, detail="Either agent_id or team_id must be provided")
|
|
84
|
+
|
|
85
|
+
agent_as_judge_eval = AgentAsJudgeEval(
|
|
86
|
+
db=db,
|
|
87
|
+
criteria=eval_run_input.criteria,
|
|
88
|
+
scoring_strategy=eval_run_input.scoring_strategy or "binary",
|
|
89
|
+
threshold=eval_run_input.threshold or 7,
|
|
90
|
+
additional_guidelines=eval_run_input.additional_guidelines,
|
|
91
|
+
name=eval_run_input.name,
|
|
92
|
+
model=default_model,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
result = await agent_as_judge_eval.arun(
|
|
96
|
+
input=eval_run_input.input, output=output, print_results=False, print_summary=False
|
|
97
|
+
)
|
|
98
|
+
if not result:
|
|
99
|
+
raise HTTPException(status_code=500, detail="Failed to run agent as judge evaluation")
|
|
100
|
+
|
|
101
|
+
eval_run = EvalSchema.from_agent_as_judge_eval(
|
|
102
|
+
agent_as_judge_eval=agent_as_judge_eval,
|
|
103
|
+
result=result,
|
|
104
|
+
agent_id=agent_id,
|
|
105
|
+
team_id=team_id,
|
|
106
|
+
model_id=model_id,
|
|
107
|
+
model_provider=model_provider,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Restore original model after eval
|
|
44
111
|
if default_model is not None:
|
|
45
112
|
if agent is not None:
|
|
46
113
|
agent.model = default_model
|
|
@@ -52,7 +119,7 @@ async def run_accuracy_eval(
|
|
|
52
119
|
|
|
53
120
|
async def run_performance_eval(
|
|
54
121
|
eval_run_input: EvalRunInput,
|
|
55
|
-
db: BaseDb,
|
|
122
|
+
db: Union[BaseDb, AsyncBaseDb],
|
|
56
123
|
agent: Optional[Agent] = None,
|
|
57
124
|
team: Optional[Team] = None,
|
|
58
125
|
default_model: Optional[Model] = None,
|
|
@@ -61,15 +128,15 @@ async def run_performance_eval(
|
|
|
61
128
|
if agent:
|
|
62
129
|
|
|
63
130
|
async def run_component(): # type: ignore
|
|
64
|
-
return await agent.arun(eval_run_input.input)
|
|
131
|
+
return await agent.arun(eval_run_input.input, stream=False)
|
|
65
132
|
|
|
66
133
|
model_id = agent.model.id if agent and agent.model else None
|
|
67
134
|
model_provider = agent.model.provider if agent and agent.model else None
|
|
68
135
|
|
|
69
136
|
elif team:
|
|
70
137
|
|
|
71
|
-
async def run_component():
|
|
72
|
-
return await team.arun(eval_run_input.input)
|
|
138
|
+
async def run_component(): # type: ignore
|
|
139
|
+
return await team.arun(eval_run_input.input, stream=False)
|
|
73
140
|
|
|
74
141
|
model_id = team.model.id if team and team.model else None
|
|
75
142
|
model_provider = team.model.provider if team and team.model else None
|
|
@@ -85,6 +152,7 @@ async def run_performance_eval(
|
|
|
85
152
|
model_id=model_id,
|
|
86
153
|
model_provider=model_provider,
|
|
87
154
|
)
|
|
155
|
+
|
|
88
156
|
result = await performance_eval.arun(print_results=False, print_summary=False)
|
|
89
157
|
if not result:
|
|
90
158
|
raise HTTPException(status_code=500, detail="Failed to run performance evaluation")
|
|
@@ -98,6 +166,7 @@ async def run_performance_eval(
|
|
|
98
166
|
model_provider=model_provider,
|
|
99
167
|
)
|
|
100
168
|
|
|
169
|
+
# Restore original model after eval
|
|
101
170
|
if default_model is not None:
|
|
102
171
|
if agent is not None:
|
|
103
172
|
agent.model = default_model
|
|
@@ -109,7 +178,7 @@ async def run_performance_eval(
|
|
|
109
178
|
|
|
110
179
|
async def run_reliability_eval(
|
|
111
180
|
eval_run_input: EvalRunInput,
|
|
112
|
-
db: BaseDb,
|
|
181
|
+
db: Union[BaseDb, AsyncBaseDb],
|
|
113
182
|
agent: Optional[Agent] = None,
|
|
114
183
|
team: Optional[Team] = None,
|
|
115
184
|
default_model: Optional[Model] = None,
|
|
@@ -119,7 +188,7 @@ async def run_reliability_eval(
|
|
|
119
188
|
raise HTTPException(status_code=400, detail="expected_tool_calls is required for reliability evaluations")
|
|
120
189
|
|
|
121
190
|
if agent:
|
|
122
|
-
agent_response = await agent.arun(eval_run_input.input)
|
|
191
|
+
agent_response = await agent.arun(eval_run_input.input, stream=False)
|
|
123
192
|
reliability_eval = ReliabilityEval(
|
|
124
193
|
db=db,
|
|
125
194
|
name=eval_run_input.name,
|
|
@@ -130,7 +199,7 @@ async def run_reliability_eval(
|
|
|
130
199
|
model_provider = agent.model.provider if agent and agent.model else None
|
|
131
200
|
|
|
132
201
|
elif team:
|
|
133
|
-
team_response = await team.arun(eval_run_input.input)
|
|
202
|
+
team_response = await team.arun(eval_run_input.input, stream=False)
|
|
134
203
|
reliability_eval = ReliabilityEval(
|
|
135
204
|
db=db,
|
|
136
205
|
name=eval_run_input.name,
|
|
@@ -152,6 +221,7 @@ async def run_reliability_eval(
|
|
|
152
221
|
model_provider=model_provider,
|
|
153
222
|
)
|
|
154
223
|
|
|
224
|
+
# Restore original model after eval
|
|
155
225
|
if default_model is not None:
|
|
156
226
|
if agent is not None:
|
|
157
227
|
agent.model = default_model
|
agno/os/routers/health.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
|
|
1
3
|
from fastapi import APIRouter
|
|
2
4
|
|
|
3
5
|
from agno.os.schema import HealthResponse
|
|
4
6
|
|
|
5
7
|
|
|
6
|
-
def get_health_router() -> APIRouter:
|
|
8
|
+
def get_health_router(health_endpoint: str = "/health") -> APIRouter:
|
|
7
9
|
router = APIRouter(tags=["Health"])
|
|
8
10
|
|
|
11
|
+
started_time_stamp = datetime.now(timezone.utc).timestamp()
|
|
12
|
+
|
|
9
13
|
@router.get(
|
|
10
|
-
|
|
14
|
+
health_endpoint,
|
|
11
15
|
operation_id="health_check",
|
|
12
16
|
summary="Health Check",
|
|
13
17
|
description="Check the health status of the AgentOS API. Returns a simple status indicator.",
|
|
@@ -15,11 +19,13 @@ def get_health_router() -> APIRouter:
|
|
|
15
19
|
responses={
|
|
16
20
|
200: {
|
|
17
21
|
"description": "API is healthy and operational",
|
|
18
|
-
"content": {
|
|
22
|
+
"content": {
|
|
23
|
+
"application/json": {"example": {"status": "ok", "instantiated_at": str(started_time_stamp)}}
|
|
24
|
+
},
|
|
19
25
|
}
|
|
20
26
|
},
|
|
21
27
|
)
|
|
22
28
|
async def health_check() -> HealthResponse:
|
|
23
|
-
return HealthResponse(status="ok")
|
|
29
|
+
return HealthResponse(status="ok", instantiated_at=str(started_time_stamp))
|
|
24
30
|
|
|
25
31
|
return router
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
3
|
import math
|
|
4
|
-
from typing import Dict, List, Optional
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
5
|
|
|
6
6
|
from fastapi import APIRouter, BackgroundTasks, Depends, File, Form, HTTPException, Path, Query, UploadFile
|
|
7
7
|
|
|
@@ -19,6 +19,9 @@ from agno.os.routers.knowledge.schemas import (
|
|
|
19
19
|
ContentStatusResponse,
|
|
20
20
|
ContentUpdateSchema,
|
|
21
21
|
ReaderSchema,
|
|
22
|
+
VectorDbSchema,
|
|
23
|
+
VectorSearchRequestSchema,
|
|
24
|
+
VectorSearchResult,
|
|
22
25
|
)
|
|
23
26
|
from agno.os.schema import (
|
|
24
27
|
BadRequestResponse,
|
|
@@ -99,6 +102,8 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
99
102
|
text_content: Optional[str] = Form(None, description="Raw text content to process"),
|
|
100
103
|
reader_id: Optional[str] = Form(None, description="ID of the reader to use for content processing"),
|
|
101
104
|
chunker: Optional[str] = Form(None, description="Chunking strategy to apply during processing"),
|
|
105
|
+
chunk_size: Optional[int] = Form(None, description="Chunk size to use for processing"),
|
|
106
|
+
chunk_overlap: Optional[int] = Form(None, description="Chunk overlap to use for processing"),
|
|
102
107
|
db_id: Optional[str] = Query(default=None, description="Database ID to use for content storage"),
|
|
103
108
|
):
|
|
104
109
|
knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
|
|
@@ -169,7 +174,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
169
174
|
content.content_hash = content_hash
|
|
170
175
|
content.id = generate_id(content_hash)
|
|
171
176
|
|
|
172
|
-
background_tasks.add_task(process_content, knowledge, content, reader_id, chunker)
|
|
177
|
+
background_tasks.add_task(process_content, knowledge, content, reader_id, chunker, chunk_size, chunk_overlap)
|
|
173
178
|
|
|
174
179
|
response = ContentResponseSchema(
|
|
175
180
|
id=content.id,
|
|
@@ -303,7 +308,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
303
308
|
}
|
|
304
309
|
},
|
|
305
310
|
)
|
|
306
|
-
def get_content(
|
|
311
|
+
async def get_content(
|
|
307
312
|
limit: Optional[int] = Query(default=20, description="Number of content entries to return"),
|
|
308
313
|
page: Optional[int] = Query(default=1, description="Page number"),
|
|
309
314
|
sort_by: Optional[str] = Query(default="created_at", description="Field to sort by"),
|
|
@@ -311,7 +316,7 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
311
316
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
312
317
|
) -> PaginatedResponse[ContentResponseSchema]:
|
|
313
318
|
knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
|
|
314
|
-
contents, count = knowledge.
|
|
319
|
+
contents, count = await knowledge.aget_content(limit=limit, page=page, sort_by=sort_by, sort_order=sort_order)
|
|
315
320
|
|
|
316
321
|
return PaginatedResponse(
|
|
317
322
|
data=[
|
|
@@ -371,13 +376,13 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
371
376
|
404: {"description": "Content not found", "model": NotFoundResponse},
|
|
372
377
|
},
|
|
373
378
|
)
|
|
374
|
-
def get_content_by_id(
|
|
379
|
+
async def get_content_by_id(
|
|
375
380
|
content_id: str,
|
|
376
381
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
377
382
|
) -> ContentResponseSchema:
|
|
378
383
|
log_info(f"Getting content by id: {content_id}")
|
|
379
384
|
knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
|
|
380
|
-
content = knowledge.
|
|
385
|
+
content = await knowledge.aget_content_by_id(content_id=content_id)
|
|
381
386
|
if not content:
|
|
382
387
|
raise HTTPException(status_code=404, detail=f"Content not found: {content_id}")
|
|
383
388
|
response = ContentResponseSchema.from_dict(
|
|
@@ -411,12 +416,12 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
411
416
|
500: {"description": "Failed to delete content", "model": InternalServerErrorResponse},
|
|
412
417
|
},
|
|
413
418
|
)
|
|
414
|
-
def delete_content_by_id(
|
|
419
|
+
async def delete_content_by_id(
|
|
415
420
|
content_id: str,
|
|
416
421
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
417
422
|
) -> ContentResponseSchema:
|
|
418
423
|
knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
|
|
419
|
-
knowledge.
|
|
424
|
+
await knowledge.aremove_content_by_id(content_id=content_id)
|
|
420
425
|
log_info(f"Deleting content by id: {content_id}")
|
|
421
426
|
|
|
422
427
|
return ContentResponseSchema(
|
|
@@ -443,7 +448,6 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
443
448
|
knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
|
|
444
449
|
log_info("Deleting all content")
|
|
445
450
|
knowledge.remove_all_content()
|
|
446
|
-
|
|
447
451
|
return "success"
|
|
448
452
|
|
|
449
453
|
@router.get(
|
|
@@ -476,13 +480,13 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
476
480
|
404: {"description": "Content not found", "model": NotFoundResponse},
|
|
477
481
|
},
|
|
478
482
|
)
|
|
479
|
-
def get_content_status(
|
|
483
|
+
async def get_content_status(
|
|
480
484
|
content_id: str,
|
|
481
485
|
db_id: Optional[str] = Query(default=None, description="The ID of the database to use"),
|
|
482
486
|
) -> ContentStatusResponse:
|
|
483
487
|
log_info(f"Getting content status: {content_id}")
|
|
484
488
|
knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
|
|
485
|
-
knowledge_status, status_message = knowledge.
|
|
489
|
+
knowledge_status, status_message = await knowledge.aget_content_status(content_id=content_id)
|
|
486
490
|
|
|
487
491
|
# Handle the case where content is not found
|
|
488
492
|
if knowledge_status is None:
|
|
@@ -513,11 +517,107 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
513
517
|
|
|
514
518
|
return ContentStatusResponse(status=status, status_message=status_message or "")
|
|
515
519
|
|
|
520
|
+
@router.post(
|
|
521
|
+
"/knowledge/search",
|
|
522
|
+
status_code=200,
|
|
523
|
+
operation_id="search_knowledge",
|
|
524
|
+
summary="Search Knowledge",
|
|
525
|
+
description="Search the knowledge base for relevant documents using query, filters and search type.",
|
|
526
|
+
response_model=PaginatedResponse[VectorSearchResult],
|
|
527
|
+
responses={
|
|
528
|
+
200: {
|
|
529
|
+
"description": "Search results retrieved successfully",
|
|
530
|
+
"content": {
|
|
531
|
+
"application/json": {
|
|
532
|
+
"example": {
|
|
533
|
+
"data": [
|
|
534
|
+
{
|
|
535
|
+
"id": "doc_123",
|
|
536
|
+
"content": "Jordan Mitchell - Software Engineer with skills in JavaScript, React, Python",
|
|
537
|
+
"name": "cv_1",
|
|
538
|
+
"meta_data": {"page": 1, "chunk": 1},
|
|
539
|
+
"usage": {"total_tokens": 14},
|
|
540
|
+
"reranking_score": 0.95,
|
|
541
|
+
"content_id": "content_456",
|
|
542
|
+
}
|
|
543
|
+
],
|
|
544
|
+
"meta": {"page": 1, "limit": 20, "total_pages": 2, "total_count": 35},
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
400: {"description": "Invalid search parameters"},
|
|
550
|
+
404: {"description": "No documents found"},
|
|
551
|
+
},
|
|
552
|
+
)
|
|
553
|
+
def search_knowledge(request: VectorSearchRequestSchema) -> PaginatedResponse[VectorSearchResult]:
|
|
554
|
+
import time
|
|
555
|
+
|
|
556
|
+
start_time = time.time()
|
|
557
|
+
|
|
558
|
+
knowledge = get_knowledge_instance_by_db_id(knowledge_instances, request.db_id)
|
|
559
|
+
|
|
560
|
+
# For now, validate the vector db ids exist in the knowledge base
|
|
561
|
+
# We will add more logic around this once we have multi vectordb support
|
|
562
|
+
# If vector db ids are provided, check if any of them match the knowledge's vector db
|
|
563
|
+
if request.vector_db_ids:
|
|
564
|
+
if knowledge.vector_db and knowledge.vector_db.id:
|
|
565
|
+
if knowledge.vector_db.id not in request.vector_db_ids:
|
|
566
|
+
raise HTTPException(
|
|
567
|
+
status_code=400,
|
|
568
|
+
detail=f"None of the provided Vector DB IDs {request.vector_db_ids} match the knowledge base Vector DB ID {knowledge.vector_db.id}",
|
|
569
|
+
)
|
|
570
|
+
else:
|
|
571
|
+
raise HTTPException(status_code=400, detail="Knowledge base has no vector database configured")
|
|
572
|
+
|
|
573
|
+
# Calculate pagination parameters
|
|
574
|
+
meta = request.meta
|
|
575
|
+
limit = meta.limit if meta and meta.limit is not None else 20
|
|
576
|
+
page = meta.page if meta and meta.page is not None else 1
|
|
577
|
+
|
|
578
|
+
# Use max_results if specified, otherwise use a higher limit for search then paginate
|
|
579
|
+
search_limit = request.max_results
|
|
580
|
+
|
|
581
|
+
results = knowledge.search(
|
|
582
|
+
query=request.query, max_results=search_limit, filters=request.filters, search_type=request.search_type
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
# Calculate pagination
|
|
586
|
+
total_results = len(results)
|
|
587
|
+
start_idx = (page - 1) * limit
|
|
588
|
+
|
|
589
|
+
# Ensure start_idx doesn't exceed the total results
|
|
590
|
+
if start_idx >= total_results and total_results > 0:
|
|
591
|
+
# If page is beyond available results, return empty results
|
|
592
|
+
paginated_results = []
|
|
593
|
+
else:
|
|
594
|
+
end_idx = min(start_idx + limit, total_results)
|
|
595
|
+
paginated_results = results[start_idx:end_idx]
|
|
596
|
+
|
|
597
|
+
search_time_ms = (time.time() - start_time) * 1000
|
|
598
|
+
|
|
599
|
+
# Convert Document objects to serializable format
|
|
600
|
+
document_results = [VectorSearchResult.from_document(doc) for doc in paginated_results]
|
|
601
|
+
|
|
602
|
+
# Calculate pagination info
|
|
603
|
+
total_pages = (total_results + limit - 1) // limit # Ceiling division
|
|
604
|
+
|
|
605
|
+
return PaginatedResponse(
|
|
606
|
+
data=document_results,
|
|
607
|
+
meta=PaginationInfo(
|
|
608
|
+
page=page,
|
|
609
|
+
limit=limit,
|
|
610
|
+
total_pages=total_pages,
|
|
611
|
+
total_count=total_results,
|
|
612
|
+
search_time_ms=search_time_ms,
|
|
613
|
+
),
|
|
614
|
+
)
|
|
615
|
+
|
|
516
616
|
@router.get(
|
|
517
617
|
"/knowledge/config",
|
|
518
618
|
status_code=200,
|
|
519
619
|
operation_id="get_knowledge_config",
|
|
520
|
-
summary="Get
|
|
620
|
+
summary="Get Config",
|
|
521
621
|
description=(
|
|
522
622
|
"Retrieve available readers, chunkers, and configuration options for content processing. "
|
|
523
623
|
"This endpoint provides metadata about supported file types, processing strategies, and filters."
|
|
@@ -703,38 +803,65 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
703
803
|
"key": "AgenticChunker",
|
|
704
804
|
"name": "AgenticChunker",
|
|
705
805
|
"description": "Chunking strategy that uses an LLM to determine natural breakpoints in the text",
|
|
806
|
+
"metadata": {"chunk_size": 5000},
|
|
706
807
|
},
|
|
707
808
|
"DocumentChunker": {
|
|
708
809
|
"key": "DocumentChunker",
|
|
709
810
|
"name": "DocumentChunker",
|
|
710
811
|
"description": "A chunking strategy that splits text based on document structure like paragraphs and sections",
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
"description": "Chunking strategy that recursively splits text into chunks by finding natural break points",
|
|
716
|
-
},
|
|
717
|
-
"SemanticChunker": {
|
|
718
|
-
"key": "SemanticChunker",
|
|
719
|
-
"name": "SemanticChunker",
|
|
720
|
-
"description": "Chunking strategy that splits text into semantic chunks using chonkie",
|
|
812
|
+
"metadata": {
|
|
813
|
+
"chunk_size": 5000,
|
|
814
|
+
"chunk_overlap": 0,
|
|
815
|
+
},
|
|
721
816
|
},
|
|
722
817
|
"FixedSizeChunker": {
|
|
723
818
|
"key": "FixedSizeChunker",
|
|
724
819
|
"name": "FixedSizeChunker",
|
|
725
820
|
"description": "Chunking strategy that splits text into fixed-size chunks with optional overlap",
|
|
821
|
+
"metadata": {
|
|
822
|
+
"chunk_size": 5000,
|
|
823
|
+
"chunk_overlap": 0,
|
|
824
|
+
},
|
|
825
|
+
},
|
|
826
|
+
"MarkdownChunker": {
|
|
827
|
+
"key": "MarkdownChunker",
|
|
828
|
+
"name": "MarkdownChunker",
|
|
829
|
+
"description": "A chunking strategy that splits markdown based on structure like headers, paragraphs and sections",
|
|
830
|
+
"metadata": {
|
|
831
|
+
"chunk_size": 5000,
|
|
832
|
+
"chunk_overlap": 0,
|
|
833
|
+
},
|
|
834
|
+
},
|
|
835
|
+
"RecursiveChunker": {
|
|
836
|
+
"key": "RecursiveChunker",
|
|
837
|
+
"name": "RecursiveChunker",
|
|
838
|
+
"description": "Chunking strategy that recursively splits text into chunks by finding natural break points",
|
|
839
|
+
"metadata": {
|
|
840
|
+
"chunk_size": 5000,
|
|
841
|
+
"chunk_overlap": 0,
|
|
842
|
+
},
|
|
726
843
|
},
|
|
727
844
|
"RowChunker": {
|
|
728
845
|
"key": "RowChunker",
|
|
729
846
|
"name": "RowChunker",
|
|
730
847
|
"description": "RowChunking chunking strategy",
|
|
848
|
+
"metadata": {},
|
|
731
849
|
},
|
|
732
|
-
"
|
|
733
|
-
"key": "
|
|
734
|
-
"name": "
|
|
735
|
-
"description": "
|
|
850
|
+
"SemanticChunker": {
|
|
851
|
+
"key": "SemanticChunker",
|
|
852
|
+
"name": "SemanticChunker",
|
|
853
|
+
"description": "Chunking strategy that splits text into semantic chunks using chonkie",
|
|
854
|
+
"metadata": {"chunk_size": 5000},
|
|
736
855
|
},
|
|
737
856
|
},
|
|
857
|
+
"vector_dbs": [
|
|
858
|
+
{
|
|
859
|
+
"id": "vector_db_1",
|
|
860
|
+
"name": "Vector DB 1",
|
|
861
|
+
"description": "Vector DB 1 description",
|
|
862
|
+
"search_types": ["vector", "keyword", "hybrid"],
|
|
863
|
+
}
|
|
864
|
+
],
|
|
738
865
|
"filters": ["filter_tag_1", "filter_tag2"],
|
|
739
866
|
}
|
|
740
867
|
}
|
|
@@ -747,8 +874,8 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
747
874
|
) -> ConfigResponseSchema:
|
|
748
875
|
knowledge = get_knowledge_instance_by_db_id(knowledge_instances, db_id)
|
|
749
876
|
|
|
750
|
-
# Get factory readers info
|
|
751
|
-
readers_info = get_all_readers_info()
|
|
877
|
+
# Get factory readers info (including custom readers from this knowledge instance)
|
|
878
|
+
readers_info = get_all_readers_info(knowledge)
|
|
752
879
|
reader_schemas = {}
|
|
753
880
|
# Add factory readers
|
|
754
881
|
for reader_info in readers_info:
|
|
@@ -760,7 +887,12 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
760
887
|
)
|
|
761
888
|
|
|
762
889
|
# Add custom readers from knowledge.readers
|
|
763
|
-
|
|
890
|
+
readers_result: Any = knowledge.get_readers() or {}
|
|
891
|
+
# Ensure readers_dict is a dictionary (defensive check)
|
|
892
|
+
if not isinstance(readers_result, dict):
|
|
893
|
+
readers_dict: Dict[str, Reader] = {}
|
|
894
|
+
else:
|
|
895
|
+
readers_dict = readers_result
|
|
764
896
|
if readers_dict:
|
|
765
897
|
for reader_id, reader in readers_dict.items():
|
|
766
898
|
# Get chunking strategies from the reader
|
|
@@ -780,8 +912,8 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
780
912
|
chunkers=chunking_strategies,
|
|
781
913
|
)
|
|
782
914
|
|
|
783
|
-
# Get content types to readers mapping
|
|
784
|
-
types_of_readers = get_content_types_to_readers_mapping()
|
|
915
|
+
# Get content types to readers mapping (including custom readers from this knowledge instance)
|
|
916
|
+
types_of_readers = get_content_types_to_readers_mapping(knowledge)
|
|
785
917
|
chunkers_list = get_all_chunkers_info()
|
|
786
918
|
|
|
787
919
|
# Convert chunkers list to dictionary format expected by schema
|
|
@@ -790,14 +922,32 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Knowledge]) -> AP
|
|
|
790
922
|
chunker_key = chunker_info.get("key")
|
|
791
923
|
if chunker_key:
|
|
792
924
|
chunkers_dict[chunker_key] = ChunkerSchema(
|
|
793
|
-
key=chunker_key,
|
|
925
|
+
key=chunker_key,
|
|
926
|
+
name=chunker_info.get("name"),
|
|
927
|
+
description=chunker_info.get("description"),
|
|
928
|
+
metadata=chunker_info.get("metadata", {}),
|
|
794
929
|
)
|
|
795
930
|
|
|
931
|
+
vector_dbs = []
|
|
932
|
+
if knowledge.vector_db:
|
|
933
|
+
search_types = knowledge.vector_db.get_supported_search_types()
|
|
934
|
+
name = knowledge.vector_db.name
|
|
935
|
+
db_id = knowledge.vector_db.id
|
|
936
|
+
vector_dbs.append(
|
|
937
|
+
VectorDbSchema(
|
|
938
|
+
id=db_id,
|
|
939
|
+
name=name,
|
|
940
|
+
description=knowledge.vector_db.description,
|
|
941
|
+
search_types=search_types,
|
|
942
|
+
)
|
|
943
|
+
)
|
|
944
|
+
|
|
796
945
|
return ConfigResponseSchema(
|
|
797
946
|
readers=reader_schemas,
|
|
947
|
+
vector_dbs=vector_dbs,
|
|
798
948
|
readersForType=types_of_readers,
|
|
799
949
|
chunkers=chunkers_dict,
|
|
800
|
-
filters=knowledge.
|
|
950
|
+
filters=knowledge.get_valid_filters(),
|
|
801
951
|
)
|
|
802
952
|
|
|
803
953
|
return router
|
|
@@ -808,33 +958,41 @@ async def process_content(
|
|
|
808
958
|
content: Content,
|
|
809
959
|
reader_id: Optional[str] = None,
|
|
810
960
|
chunker: Optional[str] = None,
|
|
961
|
+
chunk_size: Optional[int] = None,
|
|
962
|
+
chunk_overlap: Optional[int] = None,
|
|
811
963
|
):
|
|
812
964
|
"""Background task to process the content"""
|
|
813
965
|
|
|
814
966
|
try:
|
|
815
967
|
if reader_id:
|
|
816
968
|
reader = None
|
|
817
|
-
|
|
818
|
-
|
|
969
|
+
# Use get_readers() to ensure we get a dict (handles list conversion)
|
|
970
|
+
custom_readers = knowledge.get_readers()
|
|
971
|
+
if custom_readers and reader_id in custom_readers:
|
|
972
|
+
reader = custom_readers[reader_id]
|
|
973
|
+
log_debug(f"Found custom reader: {reader.__class__.__name__}")
|
|
819
974
|
else:
|
|
975
|
+
# Try to resolve from factory readers
|
|
820
976
|
key = reader_id.lower().strip().replace("-", "_").replace(" ", "_")
|
|
821
977
|
candidates = [key] + ([key[:-6]] if key.endswith("reader") else [])
|
|
822
978
|
for cand in candidates:
|
|
823
979
|
try:
|
|
824
980
|
reader = ReaderFactory.create_reader(cand)
|
|
825
|
-
log_debug(f"Resolved reader: {reader.__class__.__name__}")
|
|
981
|
+
log_debug(f"Resolved reader from factory: {reader.__class__.__name__}")
|
|
826
982
|
break
|
|
827
983
|
except Exception:
|
|
828
984
|
continue
|
|
829
985
|
if reader:
|
|
830
986
|
content.reader = reader
|
|
987
|
+
else:
|
|
988
|
+
log_debug(f"Could not resolve reader with id: {reader_id}")
|
|
831
989
|
if chunker and content.reader:
|
|
832
990
|
# Set the chunker name on the reader - let the reader handle it internally
|
|
833
|
-
content.reader.set_chunking_strategy_from_string(chunker)
|
|
991
|
+
content.reader.set_chunking_strategy_from_string(chunker, chunk_size=chunk_size, overlap=chunk_overlap)
|
|
834
992
|
log_debug(f"Set chunking strategy: {chunker}")
|
|
835
993
|
|
|
836
994
|
log_debug(f"Using reader: {content.reader.__class__.__name__}")
|
|
837
|
-
await knowledge.
|
|
995
|
+
await knowledge._load_content_async(content, upsert=False, skip_if_exists=True)
|
|
838
996
|
log_info(f"Content {content.id} processed successfully")
|
|
839
997
|
except Exception as e:
|
|
840
998
|
log_info(f"Error processing content: {e}")
|