agentgraph-server 0.5.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.
- agentgraph/__init__.py +1 -0
- agentgraph/auth/__init__.py +0 -0
- agentgraph/auth/credentials.py +224 -0
- agentgraph/backends/__init__.py +50 -0
- agentgraph/backends/sqlite/__init__.py +1 -0
- agentgraph/backends/sqlite/backend.py +1471 -0
- agentgraph/backends/sqlite/vector.py +142 -0
- agentgraph/cli.py +721 -0
- agentgraph/cli_query.py +519 -0
- agentgraph/config.py +90 -0
- agentgraph/connectors/__init__.py +0 -0
- agentgraph/connectors/base.py +455 -0
- agentgraph/connectors/registry.py +78 -0
- agentgraph/connectors/status.py +244 -0
- agentgraph/core/__init__.py +0 -0
- agentgraph/core/context.py +26 -0
- agentgraph/core/runtime.py +36 -0
- agentgraph/core/storage.py +240 -0
- agentgraph/graph/__init__.py +1 -0
- agentgraph/graph/bookmark.py +87 -0
- agentgraph/graph/delete.py +17 -0
- agentgraph/graph/download.py +35 -0
- agentgraph/graph/embeddings.py +58 -0
- agentgraph/graph/fetch.py +53 -0
- agentgraph/graph/gc.py +26 -0
- agentgraph/graph/link.py +63 -0
- agentgraph/graph/person.py +40 -0
- agentgraph/graph/query.py +244 -0
- agentgraph/graph/upsert.py +49 -0
- agentgraph/logging.py +78 -0
- agentgraph/mcp/__init__.py +0 -0
- agentgraph/mcp/server.py +811 -0
- agentgraph/perf.py +43 -0
- agentgraph/server/__init__.py +0 -0
- agentgraph/server/app.py +133 -0
- agentgraph/server/cli_api.py +708 -0
- agentgraph/server/dwell.py +79 -0
- agentgraph/server/graph_api.py +46 -0
- agentgraph/server/router.py +47 -0
- agentgraph/server/sync.py +247 -0
- agentgraph/skills.py +93 -0
- agentgraph_server-0.5.0.data/data/.agents/skills/graph/SKILL.md +159 -0
- agentgraph_server-0.5.0.data/data/.agents/skills/slack-auth/SKILL.md +92 -0
- agentgraph_server-0.5.0.dist-info/METADATA +286 -0
- agentgraph_server-0.5.0.dist-info/RECORD +49 -0
- agentgraph_server-0.5.0.dist-info/WHEEL +5 -0
- agentgraph_server-0.5.0.dist-info/entry_points.txt +2 -0
- agentgraph_server-0.5.0.dist-info/licenses/LICENSE +21 -0
- agentgraph_server-0.5.0.dist-info/top_level.txt +1 -0
agentgraph/perf.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Small performance instrumentation helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from contextvars import ContextVar, Token
|
|
9
|
+
from time import perf_counter
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("agentgraph.perf")
|
|
13
|
+
|
|
14
|
+
_timing_sink: ContextVar[list[tuple[str, float]] | None] = ContextVar("timing_sink", default=None)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@contextmanager
|
|
18
|
+
def capture_timings() -> Iterator[list[tuple[str, float]]]:
|
|
19
|
+
"""Collect nested ``timed`` spans for the current async task context."""
|
|
20
|
+
timings: list[tuple[str, float]] = []
|
|
21
|
+
token: Token[list[tuple[str, float]] | None] = _timing_sink.set(timings)
|
|
22
|
+
try:
|
|
23
|
+
yield timings
|
|
24
|
+
finally:
|
|
25
|
+
_timing_sink.reset(token)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@contextmanager
|
|
29
|
+
def timed(operation: str, **fields: Any) -> Iterator[None]:
|
|
30
|
+
"""Log elapsed time for an operation at debug level."""
|
|
31
|
+
start = perf_counter()
|
|
32
|
+
try:
|
|
33
|
+
yield
|
|
34
|
+
finally:
|
|
35
|
+
elapsed_ms = (perf_counter() - start) * 1000
|
|
36
|
+
sink = _timing_sink.get()
|
|
37
|
+
if sink is not None:
|
|
38
|
+
sink.append((operation, elapsed_ms))
|
|
39
|
+
suffix = " ".join(f"{key}={value}" for key, value in fields.items())
|
|
40
|
+
if suffix:
|
|
41
|
+
logger.debug("%s completed in %.1fms %s", operation, elapsed_ms, suffix)
|
|
42
|
+
else:
|
|
43
|
+
logger.debug("%s completed in %.1fms", operation, elapsed_ms)
|
|
File without changes
|
agentgraph/server/app.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""FastAPI application: browser extension intake and server lifecycle."""
|
|
2
|
+
|
|
3
|
+
# pyright: reportUnknownMemberType=false, reportUnknownVariableType=false
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from collections.abc import AsyncGenerator
|
|
9
|
+
from contextlib import asynccontextmanager
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from time import perf_counter
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from apscheduler.schedulers.asyncio import AsyncIOScheduler # type: ignore[import-untyped]
|
|
15
|
+
from fastapi import FastAPI, Request
|
|
16
|
+
from fastapi.responses import FileResponse
|
|
17
|
+
from fastapi.staticfiles import StaticFiles
|
|
18
|
+
from pydantic import BaseModel
|
|
19
|
+
|
|
20
|
+
from agentgraph.core.context import clear_backend, set_backend
|
|
21
|
+
from agentgraph.graph.gc import run_gc
|
|
22
|
+
from agentgraph.server.cli_api import router as cli_router
|
|
23
|
+
from agentgraph.server.graph_api import router as graph_router
|
|
24
|
+
from agentgraph.server.sync import setup_sync, shutdown_poll_tasks
|
|
25
|
+
|
|
26
|
+
_STATIC_DIR = Path(__file__).parent / "static"
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
_scheduler: AsyncIOScheduler | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def viewer_url(host: str, port: int) -> str:
|
|
34
|
+
browser_host = "127.0.0.1" if host in {"", "0.0.0.0", "::"} else host
|
|
35
|
+
if ":" in browser_host and not browser_host.startswith("["):
|
|
36
|
+
browser_host = f"[{browser_host}]"
|
|
37
|
+
return f"http://{browser_host}:{port}/viewer"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@asynccontextmanager
|
|
41
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
42
|
+
global _scheduler
|
|
43
|
+
from agentgraph.backends import get_backend_class
|
|
44
|
+
from agentgraph.config import get_settings
|
|
45
|
+
from agentgraph.connectors.registry import bootstrap
|
|
46
|
+
from agentgraph.logging import configure_logging
|
|
47
|
+
|
|
48
|
+
settings = get_settings()
|
|
49
|
+
configure_logging(settings.log_level, settings.log_file)
|
|
50
|
+
|
|
51
|
+
backend_class: Any = get_backend_class(settings.backend)
|
|
52
|
+
if settings.backend == "sqlite":
|
|
53
|
+
backend = backend_class(settings.backend_sqlite_path, settings.backend_sqlite_vector_mode)
|
|
54
|
+
else:
|
|
55
|
+
backend = backend_class(settings)
|
|
56
|
+
|
|
57
|
+
await backend.initialize()
|
|
58
|
+
set_backend(backend)
|
|
59
|
+
|
|
60
|
+
bootstrap()
|
|
61
|
+
|
|
62
|
+
_scheduler = AsyncIOScheduler()
|
|
63
|
+
_scheduler.add_job(run_gc, "cron", hour=3, minute=0, id="gc")
|
|
64
|
+
setup_sync(_scheduler)
|
|
65
|
+
_scheduler.start()
|
|
66
|
+
|
|
67
|
+
logger.info("AgentGraph server started (backend=%s)", settings.backend)
|
|
68
|
+
logger.info("Web viewer: %s", viewer_url(settings.server_host, settings.server_port))
|
|
69
|
+
yield
|
|
70
|
+
|
|
71
|
+
if _scheduler:
|
|
72
|
+
_scheduler.shutdown(wait=False)
|
|
73
|
+
try:
|
|
74
|
+
await shutdown_poll_tasks()
|
|
75
|
+
finally:
|
|
76
|
+
await backend.close()
|
|
77
|
+
clear_backend()
|
|
78
|
+
logger.info("AgentGraph server stopped")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
app = FastAPI(title="AgentGraph", lifespan=lifespan)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.middleware("http")
|
|
85
|
+
async def log_request_timing(request: Request, call_next: Any) -> Any:
|
|
86
|
+
start = perf_counter()
|
|
87
|
+
response = await call_next(request)
|
|
88
|
+
elapsed_ms = (perf_counter() - start) * 1000
|
|
89
|
+
size = response.headers.get("content-length")
|
|
90
|
+
logger.debug(
|
|
91
|
+
"request %s %s -> %s in %.1fms%s",
|
|
92
|
+
request.method,
|
|
93
|
+
request.url.path,
|
|
94
|
+
response.status_code,
|
|
95
|
+
elapsed_ms,
|
|
96
|
+
f" bytes={size}" if size else "",
|
|
97
|
+
)
|
|
98
|
+
return response
|
|
99
|
+
|
|
100
|
+
app.include_router(graph_router)
|
|
101
|
+
app.include_router(cli_router)
|
|
102
|
+
app.mount("/static", StaticFiles(directory=str(_STATIC_DIR)), name="static")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ReportDwellRequest(BaseModel):
|
|
106
|
+
url: str
|
|
107
|
+
dwell_ms: int
|
|
108
|
+
meta: dict[str, str] | None = None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@app.post("/report-dwell", status_code=202)
|
|
112
|
+
async def report_dwell(req: ReportDwellRequest) -> dict[str, Any]:
|
|
113
|
+
"""
|
|
114
|
+
Receive an incremental dwell time report (either upon hitting the threshold
|
|
115
|
+
or when a tab loses focus/closes). Records the cumulative dwell time in the
|
|
116
|
+
database for ranking, and dispatches a background connector fetch if the
|
|
117
|
+
dwell time meets the threshold.
|
|
118
|
+
"""
|
|
119
|
+
from agentgraph.server.dwell import record_dwell_time
|
|
120
|
+
|
|
121
|
+
result = await record_dwell_time(req.url, req.dwell_ms, req.meta)
|
|
122
|
+
return result
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@app.get("/viewer", include_in_schema=False)
|
|
126
|
+
async def viewer() -> FileResponse:
|
|
127
|
+
"""Serve the graph viewer HTML."""
|
|
128
|
+
return FileResponse(str(_STATIC_DIR / "viewer.html"))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@app.get("/health")
|
|
132
|
+
async def health() -> dict[str, str]:
|
|
133
|
+
return {"status": "ok"}
|