swiftagentx 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 (48) hide show
  1. swiftagent/__init__.py +69 -0
  2. swiftagent/admin/__init__.py +13 -0
  3. swiftagent/admin/fastapi_admin.py +96 -0
  4. swiftagent/admin/flask_admin.py +85 -0
  5. swiftagent/admin/service.py +254 -0
  6. swiftagent/core/__init__.py +21 -0
  7. swiftagent/core/agent.py +586 -0
  8. swiftagent/core/cache.py +248 -0
  9. swiftagent/core/log_context.py +39 -0
  10. swiftagent/core/memory.py +122 -0
  11. swiftagent/core/model_client.py +178 -0
  12. swiftagent/core/parameter.py +84 -0
  13. swiftagent/core/pipeline.py +127 -0
  14. swiftagent/core/prompt.py +157 -0
  15. swiftagent/core/router.py +136 -0
  16. swiftagent/knowledge_base/__init__.py +18 -0
  17. swiftagent/knowledge_base/base.py +69 -0
  18. swiftagent/knowledge_base/document.py +22 -0
  19. swiftagent/knowledge_base/memory.py +184 -0
  20. swiftagent/knowledge_base/stage.py +66 -0
  21. swiftagent/knowledge_base/tool.py +77 -0
  22. swiftagent/middleware/__init__.py +3 -0
  23. swiftagent/middleware/base.py +90 -0
  24. swiftagent/models/__init__.py +17 -0
  25. swiftagent/models/config.py +57 -0
  26. swiftagent/models/schema.py +226 -0
  27. swiftagent/providers/__init__.py +3 -0
  28. swiftagent/providers/openai_compatible.py +205 -0
  29. swiftagent/py.typed +0 -0
  30. swiftagent/storage/__init__.py +4 -0
  31. swiftagent/storage/base.py +32 -0
  32. swiftagent/storage/memory.py +57 -0
  33. swiftagent/stream/__init__.py +4 -0
  34. swiftagent/stream/adapter.py +88 -0
  35. swiftagent/stream/builder.py +125 -0
  36. swiftagent/tools/__init__.py +11 -0
  37. swiftagent/tools/base.py +100 -0
  38. swiftagent/tools/executor.py +99 -0
  39. swiftagent/tools/registry.py +70 -0
  40. swiftagent/tools/scenario.py +168 -0
  41. swiftagent/tools/termination.py +77 -0
  42. swiftagent/web/__init__.py +0 -0
  43. swiftagent/web/fastapi_adapter.py +71 -0
  44. swiftagent/web/flask_adapter.py +110 -0
  45. swiftagentx-0.1.0.dist-info/METADATA +834 -0
  46. swiftagentx-0.1.0.dist-info/RECORD +48 -0
  47. swiftagentx-0.1.0.dist-info/WHEEL +4 -0
  48. swiftagentx-0.1.0.dist-info/licenses/LICENSE +189 -0
swiftagent/__init__.py ADDED
@@ -0,0 +1,69 @@
1
+ """
2
+ SwiftAgent — Enterprise-grade fast-response Agent framework.
3
+
4
+ Features:
5
+ - Dual-model strategy (light for classification, heavy for execution)
6
+ - Scenario toolchains (skip ReAct for high-frequency patterns)
7
+ - Three-level cache (KB / tool result / session)
8
+ - SSE streaming with fine-grained events
9
+ - Production-ready (middleware, tracing, exponential backoff)
10
+ """
11
+
12
+ from .core.agent import Agent
13
+ from .core.model_client import ModelClient, ModelResponse, DummyModelClient, ModelClientFactory
14
+ from .core.memory import Message, SessionMemory
15
+ from .core.cache import CacheManager
16
+ from .core.prompt import PromptManager, PromptTemplate
17
+ from .core.router import IntentLevel, IntentResult, IntentRouter
18
+ from .core.pipeline import PipelineStage, StageResult, RequestPipeline
19
+ from .tools.base import Tool, ToolOutput, ToolOutputType, AgentContext
20
+ from .tools.registry import ToolRegistry
21
+ from .tools.executor import ToolExecutor
22
+ from .tools.scenario import ScenarioConfig, ToolChainStep, ScenarioEngine
23
+ from .stream.adapter import SSEStreamAdapter
24
+ from .stream.builder import SSEEventBuilder
25
+ from .models.schema import AgentRequest, AgentResponse, SessionContext
26
+ from .models.config import SwiftAgentConfig, ModelTier
27
+ from .middleware.base import Middleware, MiddlewareChain
28
+ from .storage.base import StorageBackend
29
+ from .storage.memory import MemoryStorage
30
+ from .knowledge_base.document import Document, SearchResult
31
+ from .knowledge_base.base import KnowledgeBase
32
+ from .knowledge_base.memory import MemoryKnowledgeBase
33
+ from .knowledge_base.tool import KnowledgeBaseTool
34
+ from .knowledge_base.stage import KnowledgeBaseStage
35
+ from .admin.service import AdminService
36
+
37
+ __version__ = "0.1.0"
38
+
39
+ __all__ = [
40
+ # Core
41
+ "Agent",
42
+ "ModelClient", "ModelResponse", "DummyModelClient", "ModelClientFactory",
43
+ "Message", "SessionMemory",
44
+ "CacheManager",
45
+ "PromptManager", "PromptTemplate",
46
+ "IntentLevel", "IntentResult", "IntentRouter",
47
+ "PipelineStage", "StageResult", "RequestPipeline",
48
+ # Tools
49
+ "Tool", "ToolOutput", "ToolOutputType", "AgentContext",
50
+ "ToolRegistry", "ToolExecutor",
51
+ "ScenarioConfig", "ToolChainStep", "ScenarioEngine",
52
+ # Stream
53
+ "SSEStreamAdapter", "SSEEventBuilder",
54
+ # Models
55
+ "AgentRequest", "AgentResponse", "SessionContext",
56
+ "SwiftAgentConfig", "ModelTier",
57
+ # Middleware
58
+ "Middleware", "MiddlewareChain",
59
+ # Storage
60
+ "StorageBackend", "MemoryStorage",
61
+ # Knowledge Base
62
+ "Document", "SearchResult",
63
+ "KnowledgeBase", "MemoryKnowledgeBase",
64
+ "KnowledgeBaseTool", "KnowledgeBaseStage",
65
+ # Admin
66
+ "AdminService",
67
+ # Version
68
+ "__version__",
69
+ ]
@@ -0,0 +1,13 @@
1
+ """
2
+ Admin module — management API for SwiftAgent.
3
+ """
4
+
5
+ from .service import AdminService
6
+ from .flask_admin import create_flask_admin_blueprint
7
+ from .fastapi_admin import create_fastapi_admin_router
8
+
9
+ __all__ = [
10
+ "AdminService",
11
+ "create_flask_admin_blueprint",
12
+ "create_fastapi_admin_router",
13
+ ]
@@ -0,0 +1,96 @@
1
+ """
2
+ FastAPI Router for the admin API.
3
+
4
+ Usage::
5
+
6
+ from swiftagent.admin import AdminService, create_fastapi_admin_router
7
+
8
+ service = AdminService(agent)
9
+ router = create_fastapi_admin_router(service)
10
+ app.include_router(router, prefix="/admin")
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
16
+
17
+ if TYPE_CHECKING:
18
+ from .service import AdminService
19
+
20
+
21
+ def create_fastapi_admin_router(
22
+ service: "AdminService",
23
+ prefix: str = "/admin",
24
+ tags: Optional[List[str]] = None,
25
+ ):
26
+ """
27
+ Create a FastAPI APIRouter wired to *service*.
28
+
29
+ Returns a ``fastapi.APIRouter`` — include it in your FastAPI app.
30
+ """
31
+ try:
32
+ from fastapi import APIRouter
33
+ from pydantic import BaseModel
34
+ except ImportError:
35
+ raise ImportError("FastAPI is required for the admin router: pip install fastapi")
36
+
37
+ router = APIRouter(prefix=prefix, tags=tags or ["admin"])
38
+
39
+ # -- Request models --
40
+
41
+ class CacheClearRequest(BaseModel):
42
+ level: Optional[str] = None
43
+
44
+ class KBSearchRequest(BaseModel):
45
+ query: str
46
+ top_k: int = 5
47
+
48
+ class KBAddDocumentsRequest(BaseModel):
49
+ documents: List[Dict[str, Any]]
50
+
51
+ class ConfigUpdateRequest(BaseModel):
52
+ updates: Dict[str, Any] = {}
53
+
54
+ # -- Endpoints --
55
+
56
+ @router.get("/status")
57
+ async def status():
58
+ return service.get_status()
59
+
60
+ @router.get("/tools")
61
+ async def tools():
62
+ return service.get_tools()
63
+
64
+ @router.get("/cache/stats")
65
+ async def cache_stats():
66
+ return service.get_cache_stats()
67
+
68
+ @router.post("/cache/clear")
69
+ async def cache_clear(body: CacheClearRequest):
70
+ return service.clear_cache(body.level)
71
+
72
+ @router.get("/config")
73
+ async def get_config():
74
+ return service.get_config()
75
+
76
+ @router.put("/config")
77
+ async def update_config(body: ConfigUpdateRequest):
78
+ return service.update_config(body.updates)
79
+
80
+ @router.post("/kb/search")
81
+ async def kb_search(body: KBSearchRequest):
82
+ return await service.kb_search_async(body.query, top_k=body.top_k)
83
+
84
+ @router.post("/kb/documents")
85
+ async def kb_add_documents(body: KBAddDocumentsRequest):
86
+ return await service.kb_add_documents_async(body.documents)
87
+
88
+ @router.delete("/kb/documents/{doc_id}")
89
+ async def kb_delete_document(doc_id: str):
90
+ return await service.kb_delete_document_async(doc_id)
91
+
92
+ @router.get("/kb/stats")
93
+ async def kb_stats():
94
+ return await service.kb_stats_async()
95
+
96
+ return router
@@ -0,0 +1,85 @@
1
+ """
2
+ Flask Blueprint for the admin API.
3
+
4
+ Usage::
5
+
6
+ from swiftagent.admin import AdminService, create_flask_admin_blueprint
7
+
8
+ service = AdminService(agent)
9
+ bp = create_flask_admin_blueprint(service)
10
+ app.register_blueprint(bp, url_prefix="/admin")
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from .service import AdminService
19
+
20
+
21
+ def create_flask_admin_blueprint(
22
+ service: "AdminService",
23
+ url_prefix: str = "/admin",
24
+ ):
25
+ """
26
+ Create a Flask Blueprint wired to *service*.
27
+
28
+ Returns a ``flask.Blueprint`` — register it with your Flask app.
29
+ """
30
+ try:
31
+ from flask import Blueprint, jsonify, request
32
+ except ImportError:
33
+ raise ImportError("Flask is required for the admin blueprint: pip install flask")
34
+
35
+ bp = Blueprint("swiftagent_admin", __name__, url_prefix=url_prefix)
36
+
37
+ @bp.route("/status", methods=["GET"])
38
+ def status():
39
+ return jsonify(service.get_status())
40
+
41
+ @bp.route("/tools", methods=["GET"])
42
+ def tools():
43
+ return jsonify(service.get_tools())
44
+
45
+ @bp.route("/cache/stats", methods=["GET"])
46
+ def cache_stats():
47
+ return jsonify(service.get_cache_stats())
48
+
49
+ @bp.route("/cache/clear", methods=["POST"])
50
+ def cache_clear():
51
+ body = request.get_json(silent=True) or {}
52
+ level = body.get("level")
53
+ return jsonify(service.clear_cache(level))
54
+
55
+ @bp.route("/config", methods=["GET"])
56
+ def get_config():
57
+ return jsonify(service.get_config())
58
+
59
+ @bp.route("/config", methods=["PUT"])
60
+ def update_config():
61
+ body = request.get_json(silent=True) or {}
62
+ return jsonify(service.update_config(body))
63
+
64
+ @bp.route("/kb/search", methods=["POST"])
65
+ def kb_search():
66
+ body = request.get_json(silent=True) or {}
67
+ query = body.get("query", "")
68
+ top_k = body.get("top_k", 5)
69
+ return jsonify(service.kb_search(query, top_k=top_k))
70
+
71
+ @bp.route("/kb/documents", methods=["POST"])
72
+ def kb_add_documents():
73
+ body = request.get_json(silent=True) or {}
74
+ documents = body.get("documents", [])
75
+ return jsonify(service.kb_add_documents(documents))
76
+
77
+ @bp.route("/kb/documents/<doc_id>", methods=["DELETE"])
78
+ def kb_delete_document(doc_id: str):
79
+ return jsonify(service.kb_delete_document(doc_id))
80
+
81
+ @bp.route("/kb/stats", methods=["GET"])
82
+ def kb_stats():
83
+ return jsonify(service.kb_stats())
84
+
85
+ return bp
@@ -0,0 +1,254 @@
1
+ """
2
+ AdminService — framework-agnostic management logic for a SwiftAgent agent.
3
+
4
+ Provides status queries, configuration management, cache operations,
5
+ and knowledge base management. Web framework adapters (Flask, FastAPI)
6
+ call into this service layer.
7
+
8
+ WARNING: Admin endpoints expose agent internals. Always add authentication
9
+ middleware (Flask before_request / FastAPI Depends) in production.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import asyncio
15
+ import concurrent.futures
16
+ import logging
17
+ import re
18
+ import time
19
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
20
+
21
+ if TYPE_CHECKING:
22
+ from ..core.agent import Agent
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Fields that may be updated at runtime via the admin API.
27
+ _MUTABLE_CONFIG_FIELDS = frozenset({
28
+ "max_iterations", "max_retries", "enable_cache",
29
+ "kb_cache_ttl", "code_cache_ttl", "max_input_length",
30
+ "sse_heartbeat_interval", "sse_timeout", "log_level",
31
+ "kb_exact_match_threshold", "debug",
32
+ "max_cache_entries_per_level", "enable_request_tracing",
33
+ })
34
+
35
+
36
+ def _run_sync(coro):
37
+ """Run a coroutine synchronously, safe in both sync and async contexts."""
38
+ try:
39
+ asyncio.get_running_loop()
40
+ in_async = True
41
+ except RuntimeError:
42
+ in_async = False
43
+
44
+ if in_async:
45
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
46
+ return pool.submit(asyncio.run, coro).result()
47
+ else:
48
+ return asyncio.run(coro)
49
+
50
+
51
+ class AdminService:
52
+ """
53
+ Stateless admin operations backed by an Agent instance.
54
+
55
+ Usage::
56
+
57
+ service = AdminService(agent)
58
+ status = service.get_status()
59
+ """
60
+
61
+ def __init__(self, agent: "Agent") -> None:
62
+ self.agent = agent
63
+ self._start_time = time.time()
64
+
65
+ # ---- Status ----
66
+
67
+ def get_status(self) -> Dict[str, Any]:
68
+ """Agent name, tool count, cache stats, uptime."""
69
+ return {
70
+ "name": self.agent.name,
71
+ "tools": self.agent.tool_registry.list_tools(),
72
+ "tool_count": self.agent.tool_registry.count(),
73
+ "cache_stats": self.agent.cache.get_stats(),
74
+ "has_knowledge_base": self.agent.knowledge_base is not None,
75
+ "uptime_seconds": round(time.time() - self._start_time, 1),
76
+ }
77
+
78
+ def get_tools(self) -> List[Dict[str, Any]]:
79
+ """Return JSON schema for every registered tool."""
80
+ return [
81
+ tool.get_schema()
82
+ for tool in self.agent.tool_registry.get_all().values()
83
+ ]
84
+
85
+ def get_cache_stats(self) -> Dict[str, Any]:
86
+ return self.agent.cache.get_stats()
87
+
88
+ # ---- Configuration ----
89
+
90
+ def get_config(self) -> Dict[str, Any]:
91
+ """Return current config with sensitive values masked."""
92
+ raw = self.agent.config.model_dump()
93
+ return self._mask_secrets(raw)
94
+
95
+ def update_config(self, updates: Dict[str, Any]) -> Dict[str, Any]:
96
+ """
97
+ Update config fields at runtime.
98
+
99
+ Only whitelisted mutable fields are accepted; values are validated
100
+ through Pydantic by re-constructing the config model.
101
+ Returns the full (masked) config after the update.
102
+ """
103
+ from ..models.config import SwiftAgentConfig
104
+
105
+ filtered = {k: v for k, v in updates.items() if k in _MUTABLE_CONFIG_FIELDS}
106
+ if not filtered:
107
+ return self.get_config()
108
+
109
+ current = self.agent.config.model_dump()
110
+ current.update(filtered)
111
+ try:
112
+ self.agent.config = SwiftAgentConfig(**current)
113
+ except Exception as e:
114
+ logger.warning(f"Config update rejected: {e}")
115
+ return {"error": f"Invalid config: {e}"}
116
+
117
+ return self.get_config()
118
+
119
+ # ---- Cache management ----
120
+
121
+ def clear_cache(self, level: Optional[str] = None) -> Dict[str, Any]:
122
+ """
123
+ Clear cache entries.
124
+
125
+ Args:
126
+ level: One of "level_1", "level_2", "level_3", "scenario", or None (all).
127
+
128
+ Returns:
129
+ Confirmation dict.
130
+ """
131
+ if level is None:
132
+ self.agent.cache.clear_all()
133
+ return {"cleared": "all"}
134
+
135
+ cache = self.agent.cache
136
+ if level == "level_1":
137
+ cache.level_1_cache.clear()
138
+ elif level == "level_2":
139
+ cache.level_2_cache.clear()
140
+ elif level == "level_3":
141
+ cache.level_3_cache.clear()
142
+ elif level == "scenario":
143
+ cache.clear_scenario_cache()
144
+ else:
145
+ return {"error": f"Unknown cache level: {level}"}
146
+
147
+ return {"cleared": level}
148
+
149
+ # ---- Knowledge base (sync wrappers) ----
150
+
151
+ def kb_search(self, query: str, top_k: int = 5) -> List[Dict[str, Any]]:
152
+ """Synchronous wrapper — safe in both sync and async contexts."""
153
+ kb = self.agent.knowledge_base
154
+ if kb is None:
155
+ return []
156
+ results = _run_sync(kb.search(query, top_k=top_k))
157
+ return self._format_search_results(results)
158
+
159
+ def kb_add_documents(self, documents: List[Dict[str, Any]]) -> Dict[str, Any]:
160
+ kb = self.agent.knowledge_base
161
+ if kb is None:
162
+ return {"error": "No knowledge base configured"}
163
+ from ..knowledge_base.document import Document
164
+ try:
165
+ docs = [Document(**d) for d in documents]
166
+ except Exception as e:
167
+ return {"error": f"Invalid document data: {e}"}
168
+ added = _run_sync(kb.add_documents(docs))
169
+ return {"added": added}
170
+
171
+ def kb_delete_document(self, doc_id: str) -> Dict[str, Any]:
172
+ kb = self.agent.knowledge_base
173
+ if kb is None:
174
+ return {"error": "No knowledge base configured"}
175
+ deleted = _run_sync(kb.delete_document(doc_id))
176
+ return {"deleted": deleted, "doc_id": doc_id}
177
+
178
+ def kb_stats(self) -> Dict[str, Any]:
179
+ kb = self.agent.knowledge_base
180
+ if kb is None:
181
+ return {"error": "No knowledge base configured"}
182
+ count = _run_sync(kb.count())
183
+ return {"document_count": count, "provider": type(kb).__name__}
184
+
185
+ # ---- Knowledge base (async) ----
186
+
187
+ async def kb_search_async(self, query: str, top_k: int = 5) -> List[Dict[str, Any]]:
188
+ kb = self.agent.knowledge_base
189
+ if kb is None:
190
+ return []
191
+ results = await kb.search(query, top_k=top_k)
192
+ return self._format_search_results(results)
193
+
194
+ async def kb_add_documents_async(self, documents: List[Dict[str, Any]]) -> Dict[str, Any]:
195
+ kb = self.agent.knowledge_base
196
+ if kb is None:
197
+ return {"error": "No knowledge base configured"}
198
+ from ..knowledge_base.document import Document
199
+ try:
200
+ docs = [Document(**d) for d in documents]
201
+ except Exception as e:
202
+ return {"error": f"Invalid document data: {e}"}
203
+ added = await kb.add_documents(docs)
204
+ return {"added": added}
205
+
206
+ async def kb_delete_document_async(self, doc_id: str) -> Dict[str, Any]:
207
+ kb = self.agent.knowledge_base
208
+ if kb is None:
209
+ return {"error": "No knowledge base configured"}
210
+ deleted = await kb.delete_document(doc_id)
211
+ return {"deleted": deleted, "doc_id": doc_id}
212
+
213
+ async def kb_stats_async(self) -> Dict[str, Any]:
214
+ kb = self.agent.knowledge_base
215
+ if kb is None:
216
+ return {"error": "No knowledge base configured"}
217
+ count = await kb.count()
218
+ return {"document_count": count, "provider": type(kb).__name__}
219
+
220
+ # ---- Helpers ----
221
+
222
+ @staticmethod
223
+ def _format_search_results(results) -> List[Dict[str, Any]]:
224
+ return [
225
+ {
226
+ "doc_id": r.document.doc_id,
227
+ "content": r.document.content,
228
+ "score": r.score,
229
+ "match_type": r.match_type,
230
+ "metadata": r.document.metadata,
231
+ }
232
+ for r in results
233
+ ]
234
+
235
+ @staticmethod
236
+ def _mask_secrets(data: Dict[str, Any]) -> Dict[str, Any]:
237
+ """Mask values that look like API keys or secrets."""
238
+ masked = {}
239
+ secret_pattern = re.compile(
240
+ r"(key|secret|token|password|credential)", re.IGNORECASE
241
+ )
242
+ for key, value in data.items():
243
+ if isinstance(value, str) and secret_pattern.search(key) and len(value) > 4:
244
+ masked[key] = value[:4] + "***"
245
+ elif isinstance(value, dict):
246
+ masked[key] = AdminService._mask_secrets(value)
247
+ elif isinstance(value, list):
248
+ masked[key] = [
249
+ AdminService._mask_secrets(item) if isinstance(item, dict) else item
250
+ for item in value
251
+ ]
252
+ else:
253
+ masked[key] = value
254
+ return masked
@@ -0,0 +1,21 @@
1
+ from .memory import Message, SessionMemory
2
+ from .model_client import ModelResponse, ModelClient, ModelClientFactory
3
+ from .cache import CacheEntry, CacheManager
4
+ from .prompt import PromptTemplate, PromptManager
5
+ from .parameter import ParameterManager, get_parameter_manager
6
+ from .log_context import set_request_id, get_request_id, RequestIdFilter
7
+ from .agent import Agent
8
+ from .router import IntentLevel, IntentResult, IntentRouter
9
+ from .pipeline import PipelineStage, StageResult, RequestPipeline
10
+
11
+ __all__ = [
12
+ "Message", "SessionMemory",
13
+ "ModelResponse", "ModelClient", "ModelClientFactory",
14
+ "CacheEntry", "CacheManager",
15
+ "PromptTemplate", "PromptManager",
16
+ "ParameterManager", "get_parameter_manager",
17
+ "set_request_id", "get_request_id", "RequestIdFilter",
18
+ "Agent",
19
+ "IntentLevel", "IntentResult", "IntentRouter",
20
+ "PipelineStage", "StageResult", "RequestPipeline",
21
+ ]