redis-agent-kit 0.1.0__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.
Files changed (68) hide show
  1. redis_agent_kit/__init__.py +222 -0
  2. redis_agent_kit/agent.py +265 -0
  3. redis_agent_kit/api/__init__.py +21 -0
  4. redis_agent_kit/api/app.py +163 -0
  5. redis_agent_kit/api/auth.py +97 -0
  6. redis_agent_kit/api/pipelines.py +240 -0
  7. redis_agent_kit/api/tasks.py +511 -0
  8. redis_agent_kit/api/v1.py +386 -0
  9. redis_agent_kit/attachments.py +225 -0
  10. redis_agent_kit/cli/__init__.py +5 -0
  11. redis_agent_kit/cli/agent.py +54 -0
  12. redis_agent_kit/cli/api_common.py +130 -0
  13. redis_agent_kit/cli/chat.py +434 -0
  14. redis_agent_kit/cli/health.py +21 -0
  15. redis_agent_kit/cli/main.py +173 -0
  16. redis_agent_kit/cli/pipelines.py +327 -0
  17. redis_agent_kit/cli/task.py +298 -0
  18. redis_agent_kit/cli/up.py +67 -0
  19. redis_agent_kit/cli/worker.py +103 -0
  20. redis_agent_kit/config.py +209 -0
  21. redis_agent_kit/core.py +575 -0
  22. redis_agent_kit/emitter.py +126 -0
  23. redis_agent_kit/keys.py +119 -0
  24. redis_agent_kit/knowledge.py +188 -0
  25. redis_agent_kit/mcp/__init__.py +5 -0
  26. redis_agent_kit/mcp/server.py +340 -0
  27. redis_agent_kit/memory.py +700 -0
  28. redis_agent_kit/middleware.py +481 -0
  29. redis_agent_kit/models.py +329 -0
  30. redis_agent_kit/pipelines/__init__.py +64 -0
  31. redis_agent_kit/pipelines/artifact_storage.py +189 -0
  32. redis_agent_kit/pipelines/config_loader.py +113 -0
  33. redis_agent_kit/pipelines/deduplicator.py +114 -0
  34. redis_agent_kit/pipelines/frontmatter.py +109 -0
  35. redis_agent_kit/pipelines/ingest_stage.py +100 -0
  36. redis_agent_kit/pipelines/models.py +209 -0
  37. redis_agent_kit/pipelines/orchestrator.py +285 -0
  38. redis_agent_kit/pipelines/pipeline.py +309 -0
  39. redis_agent_kit/pipelines/prepare_stage.py +160 -0
  40. redis_agent_kit/pipelines/processor.py +120 -0
  41. redis_agent_kit/pipelines/scraper.py +96 -0
  42. redis_agent_kit/pipelines/vector_store.py +386 -0
  43. redis_agent_kit/protocols/__init__.py +23 -0
  44. redis_agent_kit/protocols/a2a/__init__.py +35 -0
  45. redis_agent_kit/protocols/a2a/adapter.py +148 -0
  46. redis_agent_kit/protocols/a2a/models.py +101 -0
  47. redis_agent_kit/protocols/a2a/router.py +196 -0
  48. redis_agent_kit/protocols/acp/__init__.py +28 -0
  49. redis_agent_kit/protocols/acp/adapter.py +160 -0
  50. redis_agent_kit/protocols/acp/models.py +65 -0
  51. redis_agent_kit/protocols/acp/router.py +108 -0
  52. redis_agent_kit/protocols/base.py +134 -0
  53. redis_agent_kit/protocols/unified.py +111 -0
  54. redis_agent_kit/side_effects.py +381 -0
  55. redis_agent_kit/streaming.py +148 -0
  56. redis_agent_kit/task_manager.py +601 -0
  57. redis_agent_kit/thread_manager.py +176 -0
  58. redis_agent_kit/tools/__init__.py +33 -0
  59. redis_agent_kit/tools/decorator.py +553 -0
  60. redis_agent_kit/tools/knowledge_provider.py +82 -0
  61. redis_agent_kit/tools/manager.py +141 -0
  62. redis_agent_kit/tools/models.py +67 -0
  63. redis_agent_kit/tools/provider.py +106 -0
  64. redis_agent_kit/vectorizer.py +197 -0
  65. redis_agent_kit-0.1.0.dist-info/METADATA +198 -0
  66. redis_agent_kit-0.1.0.dist-info/RECORD +68 -0
  67. redis_agent_kit-0.1.0.dist-info/WHEEL +4 -0
  68. redis_agent_kit-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,222 @@
1
+ """Redis Agent Kit - Reusable infrastructure for building AI agents with Redis."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ # Core (AgentKit handles background processing internally)
6
+ # Pipelines (import submodule for namespaced access)
7
+ # Tools
8
+ from redis_agent_kit import pipelines, tools
9
+
10
+ # Agent and Knowledge
11
+ from redis_agent_kit.agent import Agent, AgentConfig
12
+
13
+ # API Auth (optional – requires the [api] extra for FastAPI)
14
+ try:
15
+ from redis_agent_kit.api.auth import (
16
+ create_api_key_dependency,
17
+ create_optional_api_key_dependency,
18
+ )
19
+ except ImportError: # pragma: no cover - exercised only without [api]
20
+ create_api_key_dependency = None # type: ignore[assignment]
21
+ create_optional_api_key_dependency = None # type: ignore[assignment]
22
+
23
+ # Attachments
24
+ from redis_agent_kit.attachments import (
25
+ AttachmentStore,
26
+ InlineAttachmentStore,
27
+ RedisAttachmentStore,
28
+ get_attachment_data,
29
+ )
30
+
31
+ # Config
32
+ from redis_agent_kit.config import (
33
+ MemorySettings,
34
+ ModelSettings,
35
+ Settings,
36
+ SideEffectSettings,
37
+ TaskSettings,
38
+ get_settings,
39
+ reset_settings,
40
+ set_settings,
41
+ )
42
+ from redis_agent_kit.core import (
43
+ AgentCallable,
44
+ AgentKit,
45
+ ConcurrencyConfig,
46
+ ContextAgentCallable,
47
+ RetryConfig,
48
+ create_task_with_session,
49
+ )
50
+
51
+ # Emitter
52
+ from redis_agent_kit.emitter import TaskEmitter
53
+
54
+ # Keys
55
+ from redis_agent_kit.keys import RedisKeys
56
+ from redis_agent_kit.knowledge import KnowledgeStore
57
+
58
+ # Memory
59
+ from redis_agent_kit.memory import Memory, MemorySearchResults, MessageList, NoOpMemory
60
+
61
+ # Task Middleware
62
+ # Pipeline Middleware
63
+ from redis_agent_kit.middleware import (
64
+ EmitterMiddleware,
65
+ MemoryMiddleware,
66
+ MiddlewareChain,
67
+ PipelineContext,
68
+ PipelineLoggingMiddleware,
69
+ PipelineMiddleware,
70
+ PipelineMiddlewareChain,
71
+ PipelineTimingMiddleware,
72
+ PipelineValidationMiddleware,
73
+ RAGMiddleware,
74
+ ResultMiddleware,
75
+ TaskContext,
76
+ TaskMiddleware,
77
+ )
78
+
79
+ # Models
80
+ from redis_agent_kit.models import (
81
+ AgentCard,
82
+ AgentManifest,
83
+ AgentMetadata,
84
+ AgentStatus,
85
+ Attachment,
86
+ AttachmentType,
87
+ Caller,
88
+ CallerType,
89
+ Capability,
90
+ InputRequest,
91
+ Message,
92
+ Skill,
93
+ TaskState,
94
+ TaskStatus,
95
+ TaskUpdate,
96
+ )
97
+
98
+ # Side Effects (reentrant tasks)
99
+ from redis_agent_kit.side_effects import (
100
+ DEFAULT_KEY_POLICY,
101
+ FUNCTION_NAME,
102
+ FUNCTION_SOURCE,
103
+ INPUTS,
104
+ clear_side_effects,
105
+ side_effect,
106
+ )
107
+
108
+ # Streaming
109
+ from redis_agent_kit.streaming import ChannelScope, StreamConfig
110
+
111
+ # Managers
112
+ from redis_agent_kit.task_manager import TaskManager
113
+ from redis_agent_kit.thread_manager import ThreadManager
114
+ from redis_agent_kit.tools import (
115
+ Tool,
116
+ ToolCapability,
117
+ ToolDefinition,
118
+ ToolManager,
119
+ ToolMetadata,
120
+ ToolProvider,
121
+ )
122
+
123
+ # Vectorizer
124
+ from redis_agent_kit.vectorizer import LiteLLMVectorizerAdapter, Vectorizer
125
+
126
+ __all__ = [
127
+ # Core
128
+ "AgentKit",
129
+ "AgentCallable",
130
+ "ContextAgentCallable",
131
+ "create_task_with_session",
132
+ # Task configuration helpers
133
+ "RetryConfig",
134
+ "ConcurrencyConfig",
135
+ # Config
136
+ "Settings",
137
+ "MemorySettings",
138
+ "TaskSettings",
139
+ "ModelSettings",
140
+ "SideEffectSettings",
141
+ "get_settings",
142
+ "set_settings",
143
+ "reset_settings",
144
+ # Side Effects (reentrant tasks)
145
+ "side_effect",
146
+ "clear_side_effects",
147
+ "FUNCTION_SOURCE",
148
+ "FUNCTION_NAME",
149
+ "INPUTS",
150
+ "DEFAULT_KEY_POLICY",
151
+ # Memory
152
+ "Memory",
153
+ "MessageList",
154
+ "MemorySearchResults",
155
+ "NoOpMemory",
156
+ # Managers
157
+ "TaskManager",
158
+ "ThreadManager",
159
+ # Emitter
160
+ "TaskEmitter",
161
+ # Task Middleware
162
+ "TaskMiddleware",
163
+ "TaskContext",
164
+ "MiddlewareChain",
165
+ "EmitterMiddleware",
166
+ "RAGMiddleware",
167
+ "MemoryMiddleware",
168
+ "ResultMiddleware",
169
+ # Pipeline Middleware
170
+ "PipelineMiddleware",
171
+ "PipelineContext",
172
+ "PipelineMiddlewareChain",
173
+ "PipelineLoggingMiddleware",
174
+ "PipelineTimingMiddleware",
175
+ "PipelineValidationMiddleware",
176
+ # Models
177
+ "AgentCard",
178
+ "AgentManifest",
179
+ "AgentMetadata",
180
+ "AgentStatus",
181
+ "Attachment",
182
+ "AttachmentType",
183
+ "Caller",
184
+ "CallerType",
185
+ "Capability",
186
+ "InputRequest",
187
+ "Message",
188
+ "Skill",
189
+ "TaskState",
190
+ "TaskStatus",
191
+ # Attachment Storage
192
+ "AttachmentStore",
193
+ "RedisAttachmentStore",
194
+ "InlineAttachmentStore",
195
+ "get_attachment_data",
196
+ "TaskUpdate",
197
+ # Keys
198
+ "RedisKeys",
199
+ # API Auth
200
+ "create_api_key_dependency",
201
+ "create_optional_api_key_dependency",
202
+ # Pipelines submodule
203
+ "pipelines",
204
+ # Agent and Knowledge
205
+ "Agent",
206
+ "AgentConfig",
207
+ "KnowledgeStore",
208
+ # Vectorizer
209
+ "Vectorizer",
210
+ "LiteLLMVectorizerAdapter",
211
+ # Tools
212
+ "tools",
213
+ "Tool",
214
+ "ToolCapability",
215
+ "ToolDefinition",
216
+ "ToolManager",
217
+ "ToolMetadata",
218
+ "ToolProvider",
219
+ # Streaming
220
+ "StreamConfig",
221
+ "ChannelScope",
222
+ ]
@@ -0,0 +1,265 @@
1
+ """Framework-agnostic Agent base class using LiteLLM."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from dataclasses import dataclass
8
+ from typing import Any
9
+
10
+ import litellm
11
+ import redis.asyncio as redis
12
+
13
+ from redis_agent_kit.knowledge import KnowledgeStore
14
+ from redis_agent_kit.tools import ToolManager, ToolProvider
15
+ from redis_agent_kit.tools.knowledge_provider import KnowledgeProvider
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class Message:
22
+ """A message in the conversation."""
23
+
24
+ role: str # "system", "user", "assistant", "tool"
25
+ content: str | None = None
26
+ tool_calls: list[Any] | None = None
27
+ tool_call_id: str | None = None
28
+
29
+ def to_dict(self) -> dict[str, Any]:
30
+ """Convert to dict for LiteLLM."""
31
+ d: dict[str, Any] = {"role": self.role}
32
+ if self.content is not None:
33
+ d["content"] = self.content
34
+ if self.tool_calls:
35
+ d["tool_calls"] = self.tool_calls
36
+ if self.tool_call_id:
37
+ d["tool_call_id"] = self.tool_call_id
38
+ return d
39
+
40
+
41
+ @dataclass
42
+ class AgentConfig:
43
+ """Configuration for Agent."""
44
+
45
+ model: str = "gpt-4o-mini"
46
+ system_prompt: str | None = None
47
+ temperature: float = 0.7
48
+ max_tokens: int | None = None
49
+ max_tool_iterations: int = 10
50
+ use_knowledge: bool = True
51
+ use_tools: bool = True
52
+ knowledge_context_limit: int = 5
53
+
54
+
55
+ class Agent:
56
+ """Framework-agnostic agent with LiteLLM backend.
57
+
58
+ Example:
59
+ agent = Agent(
60
+ model="gpt-4o-mini", # Or "anthropic/claude-3-5-sonnet"
61
+ redis_url="redis://localhost:6379",
62
+ )
63
+
64
+ # Ingest knowledge
65
+ await agent.knowledge.ingest("Redis uses RDB for persistence...")
66
+
67
+ # Add custom tools
68
+ agent.tools.add_provider(MyProvider())
69
+
70
+ # Chat
71
+ response = await agent.chat("How does Redis persistence work?")
72
+ """
73
+
74
+ def __init__(
75
+ self,
76
+ model: str = "gpt-4o-mini",
77
+ system_prompt: str | None = None,
78
+ redis_url: str = "redis://localhost:6379",
79
+ redis_client: redis.Redis | None = None,
80
+ config: AgentConfig | None = None,
81
+ ):
82
+ """Initialize agent.
83
+
84
+ Args:
85
+ model: LiteLLM model name (e.g., "gpt-4o-mini", "anthropic/claude-3-5-sonnet").
86
+ system_prompt: Optional system prompt.
87
+ redis_url: Redis connection URL.
88
+ redis_client: Optional existing Redis client.
89
+ config: Optional full configuration.
90
+ """
91
+ self._config = config or AgentConfig(model=model, system_prompt=system_prompt)
92
+ self._client = redis_client or redis.from_url(redis_url, decode_responses=True) # type: ignore[no-untyped-call]
93
+
94
+ # Core components
95
+ self.knowledge = KnowledgeStore(self._client)
96
+ self.tools = ToolManager()
97
+
98
+ # Register built-in knowledge tool
99
+ self._knowledge_provider = KnowledgeProvider(self.knowledge)
100
+ self.tools.add_provider(self._knowledge_provider)
101
+
102
+ # Conversation history
103
+ self._history: list[Message] = []
104
+
105
+ @property
106
+ def model(self) -> str:
107
+ """Get the model name."""
108
+ return self._config.model
109
+
110
+ @property
111
+ def config(self) -> AgentConfig:
112
+ """Get agent configuration."""
113
+ return self._config
114
+
115
+ def add_provider(self, provider: ToolProvider) -> None:
116
+ """Add a tool provider.
117
+
118
+ Args:
119
+ provider: ToolProvider instance.
120
+ """
121
+ self.tools.add_provider(provider)
122
+
123
+ def clear_history(self) -> None:
124
+ """Clear conversation history."""
125
+ self._history.clear()
126
+
127
+ async def chat(
128
+ self,
129
+ message: str,
130
+ use_knowledge: bool | None = None,
131
+ use_tools: bool | None = None,
132
+ ) -> str:
133
+ """Send a message and get a response.
134
+
135
+ Args:
136
+ message: User message.
137
+ use_knowledge: Override config to enable/disable knowledge context.
138
+ use_tools: Override config to enable/disable tools.
139
+
140
+ Returns:
141
+ Assistant response.
142
+ """
143
+ use_knowledge = use_knowledge if use_knowledge is not None else self._config.use_knowledge
144
+ use_tools = use_tools if use_tools is not None else self._config.use_tools
145
+
146
+ messages = self._build_messages(message, use_knowledge=use_knowledge)
147
+
148
+ # Get tools
149
+ tools = None
150
+ if use_tools and self.tools._tools:
151
+ tools = self.tools.get_openai_tools()
152
+
153
+ # Call LLM
154
+ response = await self._call_llm(messages, tools)
155
+
156
+ # Handle tool calls
157
+ iterations = 0
158
+ while (
159
+ response.choices[0].message.tool_calls and iterations < self._config.max_tool_iterations
160
+ ):
161
+ response = await self._handle_tool_calls(response, messages)
162
+ iterations += 1
163
+
164
+ # Extract response
165
+ assistant_content = response.choices[0].message.content or ""
166
+
167
+ # Store in history
168
+ self._history.append(Message(role="user", content=message))
169
+ self._history.append(Message(role="assistant", content=assistant_content))
170
+
171
+ return assistant_content
172
+
173
+ def _build_messages(
174
+ self,
175
+ user_message: str,
176
+ use_knowledge: bool = True,
177
+ ) -> list[dict[str, Any]]:
178
+ """Build message list for LLM call."""
179
+ messages: list[dict[str, Any]] = []
180
+
181
+ # System prompt
182
+ if self._config.system_prompt:
183
+ messages.append({"role": "system", "content": self._config.system_prompt})
184
+
185
+ # History
186
+ for msg in self._history:
187
+ messages.append(msg.to_dict())
188
+
189
+ # User message
190
+ messages.append({"role": "user", "content": user_message})
191
+
192
+ return messages
193
+
194
+ async def _call_llm(
195
+ self,
196
+ messages: list[dict[str, Any]],
197
+ tools: list[dict[str, Any]] | None = None,
198
+ ) -> Any:
199
+ """Call LLM via LiteLLM."""
200
+ kwargs: dict[str, Any] = {
201
+ "model": self._config.model,
202
+ "messages": messages,
203
+ "temperature": self._config.temperature,
204
+ }
205
+
206
+ if self._config.max_tokens:
207
+ kwargs["max_tokens"] = self._config.max_tokens
208
+
209
+ if tools:
210
+ kwargs["tools"] = tools
211
+
212
+ return await litellm.acompletion(**kwargs)
213
+
214
+ async def _handle_tool_calls(
215
+ self,
216
+ response: Any,
217
+ messages: list[dict[str, Any]],
218
+ ) -> Any:
219
+ """Execute tool calls and continue conversation."""
220
+ assistant_msg = response.choices[0].message
221
+
222
+ # Add assistant message with tool calls
223
+ messages.append(
224
+ {
225
+ "role": "assistant",
226
+ "content": assistant_msg.content,
227
+ "tool_calls": [
228
+ {
229
+ "id": tc.id,
230
+ "type": "function",
231
+ "function": {
232
+ "name": tc.function.name,
233
+ "arguments": tc.function.arguments,
234
+ },
235
+ }
236
+ for tc in assistant_msg.tool_calls
237
+ ],
238
+ }
239
+ )
240
+
241
+ # Execute each tool call
242
+ for tool_call in assistant_msg.tool_calls:
243
+ tool_name = tool_call.function.name
244
+ try:
245
+ args = json.loads(tool_call.function.arguments)
246
+ except json.JSONDecodeError:
247
+ args = {}
248
+
249
+ try:
250
+ result = await self.tools.execute(tool_name, args)
251
+ result_str = json.dumps(result) if not isinstance(result, str) else result
252
+ except Exception as e:
253
+ logger.warning(f"Tool {tool_name} failed: {e}")
254
+ result_str = json.dumps({"error": str(e)})
255
+
256
+ messages.append(
257
+ {
258
+ "role": "tool",
259
+ "tool_call_id": tool_call.id,
260
+ "content": result_str,
261
+ }
262
+ )
263
+
264
+ # Continue conversation with tool results
265
+ return await self._call_llm(messages, self.tools.get_openai_tools())
@@ -0,0 +1,21 @@
1
+ """Redis Agent Kit REST API.
2
+
3
+ The REST API requires the ``[api]`` extra (FastAPI + Uvicorn). ``create_app``
4
+ is loaded lazily so that importing :mod:`redis_agent_kit.api` on a bare
5
+ install does not eagerly fail when FastAPI is not installed.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ __all__ = ["create_app"]
11
+
12
+ if TYPE_CHECKING: # pragma: no cover
13
+ from redis_agent_kit.api.app import create_app
14
+
15
+
16
+ def __getattr__(name: str) -> Any:
17
+ if name == "create_app":
18
+ from redis_agent_kit.api.app import create_app as _create_app
19
+
20
+ return _create_app
21
+ raise AttributeError(f"module 'redis_agent_kit.api' has no attribute {name!r}")
@@ -0,0 +1,163 @@
1
+ """Redis Agent Kit REST API application."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from fastapi import Depends, FastAPI
8
+
9
+ from redis_agent_kit import __version__
10
+ from redis_agent_kit.api.auth import create_optional_api_key_dependency
11
+ from redis_agent_kit.api.pipelines import router as pipelines_router
12
+ from redis_agent_kit.api.tasks import router as tasks_router
13
+ from redis_agent_kit.api.v1 import router as v1_router
14
+ from redis_agent_kit.config import get_settings
15
+ from redis_agent_kit.models import AgentCard, AgentManifest
16
+ from redis_agent_kit.protocols.a2a import create_a2a_router
17
+ from redis_agent_kit.protocols.acp import create_acp_router
18
+ from redis_agent_kit.streaming import StreamConfig
19
+
20
+
21
+ def create_app(
22
+ redis_url: str | None = None,
23
+ prefix: str = "rak",
24
+ queue_name: str | None = None,
25
+ enable_a2a: bool = False,
26
+ enable_acp: bool = False,
27
+ enable_sse: bool = False,
28
+ enable_streaming: bool = False,
29
+ stream_config: StreamConfig | None = None,
30
+ agent_card: AgentCard | None = None,
31
+ agent_manifest: AgentManifest | None = None,
32
+ kit: Any = None,
33
+ api_keys: list[str] | None = None,
34
+ **kwargs: Any,
35
+ ) -> FastAPI:
36
+ """Create and configure the FastAPI application.
37
+
38
+ Args:
39
+ redis_url: Redis connection URL (defaults to settings).
40
+ prefix: Redis key prefix.
41
+ queue_name: Background worker queue name (defaults to settings).
42
+ enable_a2a: Enable A2A protocol endpoints.
43
+ enable_acp: Enable ACP protocol endpoints.
44
+ enable_sse: Enable Server-Sent Events streaming endpoint for tasks.
45
+ Deprecated – use ``stream_config`` instead.
46
+ enable_streaming: Enable lightweight Pub/Sub token streaming.
47
+ Deprecated – use ``stream_config`` instead.
48
+ stream_config: Structured streaming configuration. When provided,
49
+ ``enable_sse`` and ``enable_streaming`` are ignored.
50
+ agent_card: Agent card for A2A discovery (required if enable_a2a=True).
51
+ agent_manifest: Agent manifest for ACP discovery (required if enable_acp=True).
52
+ kit: AgentKit instance for protocol handlers (required if protocols enabled).
53
+ api_keys: Optional list of valid API keys. If provided, all endpoints
54
+ (except /, /health, /docs) require authentication via X-API-Key
55
+ header or Authorization: Bearer token.
56
+ **kwargs: Additional arguments passed to FastAPI constructor.
57
+ Common options include:
58
+ - title: API title (default: "Redis Agent Kit API")
59
+ - description: API description
60
+ - version: API version (default: package version)
61
+ - docs_url: URL for Swagger docs (default: "/docs")
62
+ - redoc_url: URL for ReDoc (default: "/redoc")
63
+ - openapi_url: URL for OpenAPI schema (default: "/openapi.json")
64
+ - middleware: List of middleware to add
65
+ - dependencies: Global dependencies
66
+ - lifespan: Lifespan context manager
67
+
68
+ Returns:
69
+ Configured FastAPI application.
70
+
71
+ Raises:
72
+ ValueError: If protocol is enabled but required config is missing.
73
+ """
74
+ # Validate protocol configuration
75
+ if enable_a2a and agent_card is None:
76
+ raise ValueError("agent_card is required when A2A is enabled")
77
+ if enable_acp and agent_manifest is None:
78
+ raise ValueError("agent_manifest is required when ACP is enabled")
79
+ if (enable_a2a or enable_acp) and kit is None:
80
+ raise ValueError("kit is required when protocols are enabled")
81
+
82
+ settings = get_settings()
83
+ resolved_redis_url = redis_url or settings.redis_url
84
+ resolved_queue_name = queue_name or settings.task.queue_name
85
+
86
+ # Create API key dependency if keys provided
87
+ auth_dependency = create_optional_api_key_dependency(api_keys)
88
+
89
+ # Set defaults that can be overridden by kwargs
90
+ fastapi_kwargs: dict[str, Any] = {
91
+ "title": "Redis Agent Kit API",
92
+ "description": "REST API for managing agent tasks and data pipelines",
93
+ "version": __version__,
94
+ }
95
+ fastapi_kwargs.update(kwargs)
96
+
97
+ app = FastAPI(**fastapi_kwargs)
98
+
99
+ # Resolve StreamConfig
100
+ if stream_config is not None:
101
+ resolved_stream_config = stream_config
102
+ else:
103
+ resolved_stream_config = StreamConfig.from_booleans(enable_sse, enable_streaming)
104
+
105
+ # Store config in app state
106
+ app.state.redis_url = resolved_redis_url
107
+ app.state.prefix = prefix
108
+ app.state.queue_name = resolved_queue_name
109
+ app.state.kit = kit
110
+ app.state.api_keys = api_keys
111
+ app.state.enable_a2a = enable_a2a
112
+ app.state.enable_acp = enable_acp
113
+ app.state.enable_mcp = bool(kwargs.get("enable_mcp", False))
114
+ app.state.default_agent_id = kwargs.get("default_agent_id", "default")
115
+ app.state.stream_config = resolved_stream_config
116
+ app.state.enable_sse = enable_sse
117
+ app.state.enable_streaming = enable_streaming
118
+
119
+ # Register routes (public - no auth required)
120
+ @app.get("/")
121
+ async def root() -> dict[str, str]:
122
+ """Get API information."""
123
+ return {
124
+ "name": "Redis Agent Kit API",
125
+ "version": __version__,
126
+ "docs": "/docs",
127
+ }
128
+
129
+ @app.get("/health")
130
+ async def health() -> dict[str, str]:
131
+ """Health check endpoint."""
132
+ return {"status": "healthy"}
133
+
134
+ # Build dependencies list for protected routers
135
+ router_dependencies = [Depends(auth_dependency)] if auth_dependency else []
136
+
137
+ # Register routers (protected if api_keys provided)
138
+ app.include_router(
139
+ tasks_router, prefix="/tasks", tags=["tasks"], dependencies=router_dependencies
140
+ )
141
+ app.include_router(
142
+ pipelines_router,
143
+ prefix="/pipelines",
144
+ tags=["pipelines"],
145
+ dependencies=router_dependencies,
146
+ )
147
+ app.include_router(
148
+ v1_router,
149
+ tags=["v1"],
150
+ dependencies=router_dependencies,
151
+ )
152
+
153
+ # Mount A2A router if enabled (protected if api_keys provided)
154
+ if enable_a2a and agent_card is not None and kit is not None:
155
+ a2a_router = create_a2a_router(agent_card=agent_card, kit=kit)
156
+ app.include_router(a2a_router, tags=["A2A"], dependencies=router_dependencies)
157
+
158
+ # Mount ACP router if enabled (protected if api_keys provided)
159
+ if enable_acp and agent_manifest is not None and kit is not None:
160
+ acp_router = create_acp_router(agent_manifest=agent_manifest, kit=kit)
161
+ app.include_router(acp_router, tags=["ACP"], dependencies=router_dependencies)
162
+
163
+ return app