feat(infra): add metrics endpoint, redis/postgres services, ollama url fix, llm fallback
This commit is contained in:
parent
dec3db9618
commit
f2c8e1d7b9
7 changed files with 86 additions and 1 deletions
9
api.py
9
api.py
|
|
@ -47,6 +47,7 @@ from extractor import SchemaExtractor
|
|||
from mconfig import PryConfig
|
||||
from mcp_production import make_fallback_server, register_all
|
||||
from mcp_sse import mcp_post_message, mcp_sse_endpoint
|
||||
from observability import get_metrics_output, setup_tracing
|
||||
from pipeline import HOOK_POINTS, get_pipeline, run_pipeline
|
||||
from pryextras import BatchProcessor, TransformEngine, recorder, streams
|
||||
from routers.auth import router as auth_router
|
||||
|
|
@ -80,6 +81,7 @@ except ImportError:
|
|||
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
"""Startup: validate deps. Shutdown: cleanup clients."""
|
||||
logger.info("pry_startup", version="3.0.0")
|
||||
setup_tracing()
|
||||
if not settings.api_key:
|
||||
logger.warning(
|
||||
"pry_api_key_unset",
|
||||
|
|
@ -2810,6 +2812,13 @@ app.include_router(health_router)
|
|||
app.include_router(scraping_router)
|
||||
app.include_router(templates_router)
|
||||
|
||||
|
||||
@app.get("/metrics", tags=["System"], summary="Prometheus metrics", include_in_schema=False)
|
||||
async def metrics() -> Response:
|
||||
"""Expose Prometheus metrics for scraping."""
|
||||
data, content_type = get_metrics_output()
|
||||
return Response(content=data, media_type=content_type)
|
||||
|
||||
# ── Review ──
|
||||
|
||||
|
||||
|
|
|
|||
1
cli.py
1
cli.py
|
|
@ -83,6 +83,7 @@ def _spinner(label: str):
|
|||
if stop:
|
||||
break
|
||||
print(f" {CYAN}{c}{NC} {label}... ", end="\r", flush=True)
|
||||
# CLI spinner runs in a daemon thread; sync sleep is acceptable here.
|
||||
time.sleep(0.1)
|
||||
|
||||
t = threading.Thread(target=_spin, daemon=True)
|
||||
|
|
|
|||
|
|
@ -80,3 +80,40 @@ services:
|
|||
volumes:
|
||||
pry-data:
|
||||
pry-sessions:
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: pry-redis
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:6379:6379"
|
||||
volumes:
|
||||
- pry-redis:/data
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
cpus: "0.5"
|
||||
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: pry-postgres
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:5432:5432"
|
||||
volumes:
|
||||
- pry-postgres:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_USER=pry
|
||||
- POSTGRES_PASSWORD=pry
|
||||
- POSTGRES_DB=pry
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
cpus: "0.5"
|
||||
|
||||
volumes:
|
||||
pry-sessions:
|
||||
pry-redis:
|
||||
pry-postgres:
|
||||
|
|
|
|||
|
|
@ -94,6 +94,35 @@ class LLMRegistry:
|
|||
break
|
||||
raise Exception(f"All LLM providers failed. Last: {last_error}")
|
||||
|
||||
async def complete_or_empty(
|
||||
self,
|
||||
prompt: str,
|
||||
system: str = "",
|
||||
provider_name: str = "",
|
||||
max_tokens: int = 1024,
|
||||
temperature: float = 0.7,
|
||||
model: str = "",
|
||||
fallback: bool = True,
|
||||
) -> LLMResponse:
|
||||
"""Complete via LLM, returning an empty response on total failure.
|
||||
|
||||
Use this at API boundaries where a 500 is worse than degraded output.
|
||||
"""
|
||||
try:
|
||||
return await self.complete(
|
||||
prompt, system, provider_name, max_tokens, temperature, model, fallback
|
||||
)
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.warning("llm_complete_failed_all_providers", extra={"error": str(e)[:120]})
|
||||
return LLMResponse(
|
||||
text="",
|
||||
provider="none",
|
||||
model="",
|
||||
input_tokens=0,
|
||||
output_tokens=0,
|
||||
cost_usd=0.0,
|
||||
)
|
||||
|
||||
async def embed(self, text: str, provider_name: str = "", model: str = "") -> list[float]:
|
||||
names = [provider_name] if provider_name else list(self.fallback_chain)
|
||||
for name in names:
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ dependencies = [
|
|||
"structlog>=24.0.0",
|
||||
"sqlalchemy>=2.0.0",
|
||||
"aiosqlite>=0.19.0",
|
||||
"prometheus-client>=0.21.0",
|
||||
"opentelemetry-api>=1.29.0",
|
||||
"opentelemetry-sdk>=1.29.0",
|
||||
"opentelemetry-instrumentation-fastapi>=0.50b0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class PrySettings(BaseSettings):
|
|||
port: int = 8002
|
||||
|
||||
# Ollama LLM endpoint
|
||||
ollama_url: str = "http://100.100.18.18:11434"
|
||||
ollama_url: str = "http://100.104.130.92:11434"
|
||||
|
||||
# FlareSolverr Cloudflare bypass endpoint
|
||||
flaresolverr_url: str = "http://flaresolverr:8191/v1"
|
||||
|
|
|
|||
|
|
@ -314,6 +314,11 @@ class UltimateScraper:
|
|||
|
||||
driver = uc.Chrome(headless=True, use_subprocess=True)
|
||||
driver.get(url)
|
||||
# Note: time.sleep is used inside a sync thread-pool task because
|
||||
# undetected_chromedriver's blocking API runs in asyncio.to_thread.
|
||||
# Replacing with asyncio.sleep would require restructuring the tier
|
||||
# to be fully async; this sync sleep is bounded and does not block
|
||||
# the event loop directly.
|
||||
time.sleep(random.uniform(2, 4))
|
||||
html = driver.page_source
|
||||
driver.quit()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue