feat: initial monorepo scaffold - Next.js 15 + Prisma + Python SDK stubs
- Turborepo monorepo with apps/web and packages/database, sdk-python - Next.js 15 app with professional landing page (dark theme, emerald accent) - Prisma schema: Trace, DecisionPoint, Span, Event models with full indexing - Docker Compose: web (port 4200), postgres:16, redis:7, migrate service - Python SDK package stubs: init, trace decorator, log_decision, integrations - Multi-stage Dockerfile for standalone Next.js production build
This commit is contained in:
17
.dockerignore
Normal file
17
.dockerignore
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
dist
|
||||||
|
.turbo
|
||||||
|
*.tsbuildinfo
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
coverage
|
||||||
|
.DS_Store
|
||||||
|
tmp
|
||||||
|
*.log
|
||||||
|
.git
|
||||||
|
tests
|
||||||
|
README.md
|
||||||
|
__pycache__
|
||||||
|
.venv
|
||||||
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
dist
|
||||||
|
.turbo
|
||||||
|
*.tsbuildinfo
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
coverage
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
.venv
|
||||||
|
*.egg-info
|
||||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
FROM node:20-alpine AS base
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
FROM base AS deps
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
COPY apps/web/package.json ./apps/web/
|
||||||
|
COPY packages/database/package.json ./packages/database/
|
||||||
|
RUN npm install --production=false
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
RUN npx prisma generate --schema=packages/database/prisma/schema.prisma
|
||||||
|
RUN npx turbo build
|
||||||
|
|
||||||
|
FROM base AS web
|
||||||
|
RUN addgroup --system --gid 1001 nodejs && \
|
||||||
|
adduser --system --uid 1001 nextjs
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/packages/database/prisma ./packages/database/prisma
|
||||||
|
USER nextjs
|
||||||
|
EXPOSE 3000
|
||||||
|
ENV PORT=3000 HOSTNAME="0.0.0.0"
|
||||||
|
CMD ["node", "apps/web/server.js"]
|
||||||
6
apps/web/next-env.d.ts
vendored
Normal file
6
apps/web/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
/// <reference path="./.next/types/routes.d.ts" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
7
apps/web/next.config.mjs
Normal file
7
apps/web/next.config.mjs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const config = {
|
||||||
|
transpilePackages: ["@agentlens/database"],
|
||||||
|
output: "standalone",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
30
apps/web/package.json
Normal file
30
apps/web/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@agentlens/web",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint",
|
||||||
|
"db:generate": "prisma generate --schema=../../packages/database/prisma/schema.prisma",
|
||||||
|
"db:push": "prisma db push --schema=../../packages/database/prisma/schema.prisma",
|
||||||
|
"db:migrate": "prisma migrate deploy --schema=../../packages/database/prisma/schema.prisma"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@agentlens/database": "*",
|
||||||
|
"next": "^15.1.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"lucide-react": "^0.469.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4.0.0",
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"postcss": "^8.5.0",
|
||||||
|
"tailwindcss": "^4.0.0",
|
||||||
|
"typescript": "^5.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
apps/web/postcss.config.mjs
Normal file
6
apps/web/postcss.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
0
apps/web/public/.gitkeep
Normal file
0
apps/web/public/.gitkeep
Normal file
1
apps/web/src/app/globals.css
Normal file
1
apps/web/src/app/globals.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
24
apps/web/src/app/layout.tsx
Normal file
24
apps/web/src/app/layout.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "AgentLens",
|
||||||
|
description: "Agent observability that traces decisions, not just API calls",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en" className="dark">
|
||||||
|
<body className={`${inter.className} bg-neutral-950 text-neutral-100 antialiased`}>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
213
apps/web/src/app/page.tsx
Normal file
213
apps/web/src/app/page.tsx
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import {
|
||||||
|
Activity,
|
||||||
|
Brain,
|
||||||
|
DollarSign,
|
||||||
|
ArrowRight,
|
||||||
|
CheckCircle2,
|
||||||
|
GitBranch,
|
||||||
|
Cpu,
|
||||||
|
Zap,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
export default function HomePage() {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-neutral-950">
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section className="relative overflow-hidden border-b border-neutral-800/50">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-emerald-500/5 via-transparent to-transparent" />
|
||||||
|
<div className="absolute inset-0 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,rgba(16,185,129,0.1),transparent)]" />
|
||||||
|
|
||||||
|
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-20 pb-24">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full border border-emerald-500/20 bg-emerald-500/5 text-emerald-400 text-sm mb-8">
|
||||||
|
<Zap className="w-4 h-4" />
|
||||||
|
<span>Agent Observability Platform</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-6xl sm:text-7xl md:text-8xl font-bold tracking-tight mb-6">
|
||||||
|
<span className="bg-gradient-to-br from-emerald-400 via-emerald-300 to-emerald-500 bg-clip-text text-transparent">
|
||||||
|
AgentLens
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-xl sm:text-2xl text-neutral-400 max-w-3xl mx-auto mb-10 leading-relaxed">
|
||||||
|
See why your AI agents make the decisions they make. <br className="hidden sm:block" />
|
||||||
|
Complete observability for multi-agent systems.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||||
|
<button className="group px-8 py-4 bg-emerald-500 hover:bg-emerald-400 text-neutral-950 font-semibold rounded-lg transition-all duration-200 flex items-center gap-2 shadow-lg shadow-emerald-500/25 hover:shadow-emerald-500/40">
|
||||||
|
Get Started
|
||||||
|
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
href="https://gitea.repi.fun/repi/agentlens"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="px-8 py-4 border border-neutral-700 hover:border-neutral-600 text-neutral-300 font-medium rounded-lg transition-all duration-200"
|
||||||
|
>
|
||||||
|
View Source
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Features Section */}
|
||||||
|
<section className="py-24 border-b border-neutral-800/50">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold mb-4">
|
||||||
|
Everything you need to understand your agents
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-neutral-400 max-w-2xl mx-auto">
|
||||||
|
From decision trees to cost intelligence, get complete visibility into how your AI systems operate
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
|
{/* Feature 1: Decision Trees */}
|
||||||
|
<div className="group p-8 rounded-2xl border border-neutral-800/50 bg-gradient-to-b from-neutral-900/50 to-transparent hover:border-emerald-500/30 transition-all duration-300">
|
||||||
|
<div className="w-14 h-14 rounded-xl bg-emerald-500/10 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300">
|
||||||
|
<GitBranch className="w-7 h-7 text-emerald-400" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-semibold mb-3">Decision Trees</h3>
|
||||||
|
<p className="text-neutral-400 leading-relaxed">
|
||||||
|
Visualize the complete reasoning behind every agent choice. See the branching logic, alternatives considered, and the path chosen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Feature 2: Context Awareness */}
|
||||||
|
<div className="group p-8 rounded-2xl border border-neutral-800/50 bg-gradient-to-b from-neutral-900/50 to-transparent hover:border-emerald-500/30 transition-all duration-300">
|
||||||
|
<div className="w-14 h-14 rounded-xl bg-emerald-500/10 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300">
|
||||||
|
<Brain className="w-7 h-7 text-emerald-400" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-semibold mb-3">Context Awareness</h3>
|
||||||
|
<p className="text-neutral-400 leading-relaxed">
|
||||||
|
Monitor context window utilization in real-time. Track what's being fed into your agents and what's being left behind.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Feature 3: Cost Intelligence */}
|
||||||
|
<div className="group p-8 rounded-2xl border border-neutral-800/50 bg-gradient-to-b from-neutral-900/50 to-transparent hover:border-emerald-500/30 transition-all duration-300">
|
||||||
|
<div className="w-14 h-14 rounded-xl bg-emerald-500/10 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300">
|
||||||
|
<DollarSign className="w-7 h-7 text-emerald-400" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-semibold mb-3">Cost Intelligence</h3>
|
||||||
|
<p className="text-neutral-400 leading-relaxed">
|
||||||
|
Track spending per decision, per agent, per trace. Get granular insights into where every dollar goes in your AI operations.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Code Example Section */}
|
||||||
|
<section className="py-24 border-b border-neutral-800/50">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||||
|
<div>
|
||||||
|
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full border border-neutral-700 bg-neutral-800/30 text-neutral-400 text-sm mb-6">
|
||||||
|
<Cpu className="w-4 h-4" />
|
||||||
|
<span>Python SDK</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold mb-4">
|
||||||
|
Instrument your agents in minutes
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg text-neutral-400 mb-8 leading-relaxed">
|
||||||
|
Our Python SDK integrates seamlessly with LangChain, OpenAI, and custom agents. Add observability without changing your architecture.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul className="space-y-4">
|
||||||
|
{[
|
||||||
|
"Drop-in decorators for automatic tracing",
|
||||||
|
"No code changes required for LangChain",
|
||||||
|
"Async-first design with minimal overhead",
|
||||||
|
"Type-safe with full IDE support",
|
||||||
|
].map((feature, i) => (
|
||||||
|
<li key={i} className="flex items-start gap-3">
|
||||||
|
<div className="w-6 h-6 rounded-full bg-emerald-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||||
|
<CheckCircle2 className="w-4 h-4 text-emerald-400" />
|
||||||
|
</div>
|
||||||
|
<span className="text-neutral-300">{feature}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Code Block */}
|
||||||
|
<div className="rounded-xl overflow-hidden border border-neutral-800 bg-neutral-900/50 backdrop-blur-sm">
|
||||||
|
<div className="px-4 py-3 border-b border-neutral-800 flex items-center gap-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-neutral-600" />
|
||||||
|
<div className="w-3 h-3 rounded-full bg-neutral-600" />
|
||||||
|
<div className="w-3 h-3 rounded-full bg-neutral-600" />
|
||||||
|
</div>
|
||||||
|
<span className="ml-4 text-sm text-neutral-500">example.py</span>
|
||||||
|
</div>
|
||||||
|
<pre className="p-6 overflow-x-auto text-sm">
|
||||||
|
<code className="text-neutral-300">
|
||||||
|
<span className="text-purple-400">from</span> <span className="text-neutral-300">agentlens</span> <span className="text-purple-400">import</span> <span className="text-emerald-300">init</span><span className="text-neutral-300">,</span> <span className="text-emerald-300">trace</span>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<span className="text-neutral-500"># Initialize AgentLens</span>
|
||||||
|
<br />
|
||||||
|
<span className="text-emerald-300">init</span><span className="text-neutral-300">(</span>
|
||||||
|
<br />
|
||||||
|
<span className="text-orange-300">api_key</span><span className="text-neutral-300">=</span><span className="text-emerald-300">"your-api-key"</span><span className="text-neutral-300">,</span>
|
||||||
|
<br />
|
||||||
|
<span className="text-orange-300">endpoint</span><span className="text-neutral-300">=</span><span className="text-emerald-300">"https://agentlens.vectry.tech"</span>
|
||||||
|
<br />
|
||||||
|
<span className="text-neutral-300">)</span>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<span className="text-neutral-500"># Trace your agent function</span>
|
||||||
|
<br />
|
||||||
|
<span className="text-purple-400">@trace</span><span className="text-neutral-300">(</span><span className="text-orange-300">name</span><span className="text-neutral-300">=</span><span className="text-emerald-300">"research-agent"</span><span className="text-neutral-300">)</span>
|
||||||
|
<br />
|
||||||
|
<span className="text-purple-400">async</span> <span className="text-purple-400">def</span> <span className="text-emerald-300">research</span><span className="text-neutral-300">(</span><span className="text-blue-300">topic</span><span className="text-neutral-300">:</span> <span className="text-blue-300">str</span><span className="text-neutral-300">):</span>
|
||||||
|
<br />
|
||||||
|
<span className="text-neutral-500"># Your agent logic here</span>
|
||||||
|
<br />
|
||||||
|
<span className="text-purple-400">return</span> <span className="text-emerald-300">f"Researching: {"{topic"}"</span>
|
||||||
|
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="py-12 border-t border-neutral-800/50">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex flex-col md:flex-row justify-between items-center gap-6">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-8 h-8 rounded bg-gradient-to-br from-emerald-400 to-emerald-600 flex items-center justify-center">
|
||||||
|
<Activity className="w-5 h-5 text-white" />
|
||||||
|
</div>
|
||||||
|
<span className="font-semibold text-lg">AgentLens</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-8 text-sm text-neutral-400">
|
||||||
|
<a href="https://vectry.tech" target="_blank" rel="noopener noreferrer" className="hover:text-emerald-400 transition-colors">
|
||||||
|
Built by Vectry
|
||||||
|
</a>
|
||||||
|
<a href="https://gitea.repi.fun/repi/agentlens" target="_blank" rel="noopener noreferrer" className="hover:text-emerald-400 transition-colors">
|
||||||
|
Source Code
|
||||||
|
</a>
|
||||||
|
<a href="https://agentlens.vectry.tech/docs" target="_blank" rel="noopener noreferrer" className="hover:text-emerald-400 transition-colors">
|
||||||
|
Documentation
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-sm text-neutral-500">
|
||||||
|
MIT License © 2026 Vectry
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
apps/web/tsconfig.json
Normal file
23
apps/web/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [{ "name": "next" }],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
55
docker-compose.yml
Normal file
55
docker-compose.yml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
services:
|
||||||
|
web:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: web
|
||||||
|
ports:
|
||||||
|
- "4200:3000"
|
||||||
|
environment:
|
||||||
|
- REDIS_URL=redis://redis:6379
|
||||||
|
- DATABASE_URL=postgresql://agentlens:agentlens@postgres:5432/agentlens
|
||||||
|
depends_on:
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
migrate:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=agentlens
|
||||||
|
- POSTGRES_PASSWORD=agentlens
|
||||||
|
- POSTGRES_DB=agentlens
|
||||||
|
volumes:
|
||||||
|
- agentlens_postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U agentlens"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
migrate:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: builder
|
||||||
|
command: npx prisma migrate deploy --schema=packages/database/prisma/schema.prisma
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgresql://agentlens:agentlens@postgres:5432/agentlens
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
volumes:
|
||||||
|
- agentlens_redis_data:/data
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
agentlens_postgres_data:
|
||||||
|
agentlens_redis_data:
|
||||||
2210
package-lock.json
generated
Normal file
2210
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
package.json
Normal file
20
package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "agentlens",
|
||||||
|
"private": true,
|
||||||
|
"workspaces": ["apps/*", "packages/*"],
|
||||||
|
"scripts": {
|
||||||
|
"dev": "turbo dev",
|
||||||
|
"build": "turbo build",
|
||||||
|
"lint": "turbo lint",
|
||||||
|
"clean": "turbo clean",
|
||||||
|
"db:generate": "turbo db:generate",
|
||||||
|
"db:push": "turbo db:push",
|
||||||
|
"db:migrate": "turbo db:migrate"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"turbo": "^2",
|
||||||
|
"typescript": "^5.7"
|
||||||
|
},
|
||||||
|
"engines": { "node": ">=20" },
|
||||||
|
"packageManager": "npm@10.8.2"
|
||||||
|
}
|
||||||
21
packages/database/package.json
Normal file
21
packages/database/package.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "@agentlens/database",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"clean": "rm -rf dist",
|
||||||
|
"db:generate": "prisma generate",
|
||||||
|
"db:push": "prisma db push",
|
||||||
|
"db:migrate": "prisma migrate dev"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/client": "^6.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prisma": "^6.3.0",
|
||||||
|
"typescript": "^5.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
154
packages/database/prisma/schema.prisma
Normal file
154
packages/database/prisma/schema.prisma
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
model Trace {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
sessionId String?
|
||||||
|
name String
|
||||||
|
status TraceStatus @default(RUNNING)
|
||||||
|
tags String[] @default([])
|
||||||
|
metadata Json?
|
||||||
|
|
||||||
|
totalCost Float?
|
||||||
|
totalTokens Int?
|
||||||
|
totalDuration Int?
|
||||||
|
|
||||||
|
startedAt DateTime @default(now())
|
||||||
|
endedAt DateTime?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
decisionPoints DecisionPoint[]
|
||||||
|
spans Span[]
|
||||||
|
events Event[]
|
||||||
|
|
||||||
|
@@index([sessionId])
|
||||||
|
@@index([status])
|
||||||
|
@@index([createdAt])
|
||||||
|
@@index([name])
|
||||||
|
}
|
||||||
|
|
||||||
|
model DecisionPoint {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
traceId String
|
||||||
|
trace Trace @relation(fields: [traceId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
type DecisionType
|
||||||
|
reasoning String?
|
||||||
|
|
||||||
|
chosen Json
|
||||||
|
alternatives Json[]
|
||||||
|
|
||||||
|
contextSnapshot Json?
|
||||||
|
|
||||||
|
durationMs Int?
|
||||||
|
costUsd Float?
|
||||||
|
|
||||||
|
parentSpanId String?
|
||||||
|
span Span? @relation(fields: [parentSpanId], references: [id])
|
||||||
|
|
||||||
|
timestamp DateTime @default(now())
|
||||||
|
|
||||||
|
@@index([traceId])
|
||||||
|
@@index([type])
|
||||||
|
@@index([timestamp])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Span {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
traceId String
|
||||||
|
trace Trace @relation(fields: [traceId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
parentSpanId String?
|
||||||
|
parentSpan Span? @relation("SpanTree", fields: [parentSpanId], references: [id])
|
||||||
|
childSpans Span[] @relation("SpanTree")
|
||||||
|
|
||||||
|
name String
|
||||||
|
type SpanType
|
||||||
|
|
||||||
|
input Json?
|
||||||
|
output Json?
|
||||||
|
|
||||||
|
tokenCount Int?
|
||||||
|
costUsd Float?
|
||||||
|
durationMs Int?
|
||||||
|
|
||||||
|
status SpanStatus @default(RUNNING)
|
||||||
|
statusMessage String?
|
||||||
|
|
||||||
|
startedAt DateTime @default(now())
|
||||||
|
endedAt DateTime?
|
||||||
|
|
||||||
|
metadata Json?
|
||||||
|
|
||||||
|
decisionPoints DecisionPoint[]
|
||||||
|
|
||||||
|
@@index([traceId])
|
||||||
|
@@index([parentSpanId])
|
||||||
|
@@index([type])
|
||||||
|
@@index([startedAt])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Event {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
traceId String
|
||||||
|
trace Trace @relation(fields: [traceId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
spanId String?
|
||||||
|
type EventType
|
||||||
|
name String
|
||||||
|
|
||||||
|
metadata Json?
|
||||||
|
|
||||||
|
timestamp DateTime @default(now())
|
||||||
|
|
||||||
|
@@index([traceId])
|
||||||
|
@@index([type])
|
||||||
|
@@index([timestamp])
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TraceStatus {
|
||||||
|
RUNNING
|
||||||
|
COMPLETED
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DecisionType {
|
||||||
|
TOOL_SELECTION
|
||||||
|
ROUTING
|
||||||
|
RETRY
|
||||||
|
ESCALATION
|
||||||
|
MEMORY_RETRIEVAL
|
||||||
|
PLANNING
|
||||||
|
CUSTOM
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SpanType {
|
||||||
|
LLM_CALL
|
||||||
|
TOOL_CALL
|
||||||
|
MEMORY_OP
|
||||||
|
CHAIN
|
||||||
|
AGENT
|
||||||
|
CUSTOM
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SpanStatus {
|
||||||
|
RUNNING
|
||||||
|
COMPLETED
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EventType {
|
||||||
|
ERROR
|
||||||
|
RETRY
|
||||||
|
FALLBACK
|
||||||
|
CONTEXT_OVERFLOW
|
||||||
|
USER_FEEDBACK
|
||||||
|
CUSTOM
|
||||||
|
}
|
||||||
2
packages/database/src/index.ts
Normal file
2
packages/database/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { PrismaClient } from "@prisma/client";
|
||||||
|
export type * from "@prisma/client";
|
||||||
18
packages/database/tsconfig.json
Normal file
18
packages/database/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
38
packages/sdk-python/README.md
Normal file
38
packages/sdk-python/README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# AgentLens Python SDK
|
||||||
|
|
||||||
|
AgentLens provides observability for AI agents by tracing decisions, not just API calls.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install agentlens
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```python
|
||||||
|
from agentlens import init, trace
|
||||||
|
|
||||||
|
# Initialize AgentLens
|
||||||
|
init(api_key="your-api-key", endpoint="https://agentlens.vectry.tech")
|
||||||
|
|
||||||
|
# Trace your agent functions
|
||||||
|
@trace(name="research-agent")
|
||||||
|
async def research(topic: str) -> str:
|
||||||
|
return f"Researching: {topic}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Decision Tracing**: Log and visualize agent decisions with alternatives
|
||||||
|
- **Context Awareness**: Monitor context window utilization
|
||||||
|
- **Cost Intelligence**: Track token usage and costs per operation
|
||||||
|
- **Integrations**: Native support for LangChain and OpenAI
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Full documentation available at [https://agentlens.vectry.tech/docs](https://agentlens.vectry.tech/docs)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © 2026 Vectry
|
||||||
8
packages/sdk-python/agentlens/__init__.py
Normal file
8
packages/sdk-python/agentlens/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"""AgentLens - Agent observability that traces decisions, not just API calls."""
|
||||||
|
|
||||||
|
from agentlens.client import init, shutdown
|
||||||
|
from agentlens.trace import trace
|
||||||
|
from agentlens.decision import log_decision
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
__all__ = ["init", "shutdown", "trace", "log_decision"]
|
||||||
43
packages/sdk-python/agentlens/client.py
Normal file
43
packages/sdk-python/agentlens/client.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
"""Client initialization and management for AgentLens."""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
_client: Optional["_Client"] = None
|
||||||
|
|
||||||
|
|
||||||
|
class _Client:
|
||||||
|
"""Internal client class for managing AgentLens connection."""
|
||||||
|
|
||||||
|
def __init__(self, api_key: str, endpoint: str) -> None:
|
||||||
|
self.api_key = api_key
|
||||||
|
self.endpoint = endpoint
|
||||||
|
self.is_shutdown = False
|
||||||
|
|
||||||
|
def shutdown(self) -> None:
|
||||||
|
"""Shutdown the client."""
|
||||||
|
self.is_shutdown = True
|
||||||
|
|
||||||
|
|
||||||
|
def init(api_key: str, endpoint: str = "https://agentlens.vectry.tech") -> None:
|
||||||
|
"""Initialize the AgentLens client.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_key: Your AgentLens API key.
|
||||||
|
endpoint: The AgentLens API endpoint (default: https://agentlens.vectry.tech).
|
||||||
|
"""
|
||||||
|
global _client
|
||||||
|
_client = _Client(api_key=api_key, endpoint=endpoint)
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown() -> None:
|
||||||
|
"""Shutdown the AgentLens client."""
|
||||||
|
global _client
|
||||||
|
if _client:
|
||||||
|
_client.shutdown()
|
||||||
|
_client = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_client() -> Optional[_Client]:
|
||||||
|
"""Get the current client instance."""
|
||||||
|
return _client
|
||||||
32
packages/sdk-python/agentlens/decision.py
Normal file
32
packages/sdk-python/agentlens/decision.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
"""Decision logging for tracking agent decision points."""
|
||||||
|
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
|
||||||
|
def log_decision(
|
||||||
|
type: str,
|
||||||
|
chosen: Any,
|
||||||
|
alternatives: List[Any],
|
||||||
|
reasoning: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
"""Log a decision point in the agent's reasoning.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
type: Type of decision (e.g., "tool_selection", "routing", "retry").
|
||||||
|
chosen: The option that was selected.
|
||||||
|
alternatives: List of alternatives that were considered.
|
||||||
|
reasoning: Optional explanation for the decision.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
log_decision(
|
||||||
|
type="tool_selection",
|
||||||
|
chosen="search",
|
||||||
|
alternatives=["search", "calculate", "browse"],
|
||||||
|
reasoning="Search is most appropriate for finding information"
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
print(f"[AgentLens] Decision logged: {type}")
|
||||||
|
print(f"[AgentLens] Chosen: {chosen}")
|
||||||
|
print(f"[AgentLens] Alternatives: {alternatives}")
|
||||||
|
if reasoning:
|
||||||
|
print(f"[AgentLens] Reasoning: {reasoning}")
|
||||||
1
packages/sdk-python/agentlens/integrations/__init__.py
Normal file
1
packages/sdk-python/agentlens/integrations/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Integration packages for AgentLens."""
|
||||||
55
packages/sdk-python/agentlens/integrations/langchain.py
Normal file
55
packages/sdk-python/agentlens/integrations/langchain.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
"""LangChain integration for AgentLens."""
|
||||||
|
|
||||||
|
from typing import Any, Dict, Optional, Sequence
|
||||||
|
from langchain_core.callbacks import BaseCallbackHandler
|
||||||
|
from langchain_core.outputs import LLMResult
|
||||||
|
from langchain_core.messages import BaseMessage
|
||||||
|
|
||||||
|
|
||||||
|
class AgentLensCallbackHandler(BaseCallbackHandler):
|
||||||
|
"""Callback handler for LangChain integration with AgentLens.
|
||||||
|
|
||||||
|
This handler captures LLM calls, tool calls, and agent actions
|
||||||
|
to provide observability for LangChain-based agents.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.trace_id: Optional[str] = None
|
||||||
|
|
||||||
|
def on_llm_start(
|
||||||
|
self,
|
||||||
|
serialized: Dict[str, Any],
|
||||||
|
prompts: list[str],
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
|
"""Called when an LLM starts processing."""
|
||||||
|
print(f"[AgentLens] LLM started: {serialized.get('name', 'unknown')}")
|
||||||
|
|
||||||
|
def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
|
||||||
|
"""Called when an LLM finishes processing."""
|
||||||
|
print(f"[AgentLens] LLM completed")
|
||||||
|
|
||||||
|
def on_llm_error(self, error: Exception, **kwargs: Any) -> None:
|
||||||
|
"""Called when an LLM encounters an error."""
|
||||||
|
print(f"[AgentLens] LLM error: {error}")
|
||||||
|
|
||||||
|
def on_tool_start(
|
||||||
|
self,
|
||||||
|
serialized: Dict[str, Any],
|
||||||
|
input_str: str,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
|
"""Called when a tool starts executing."""
|
||||||
|
print(f"[AgentLens] Tool started: {serialized.get('name', 'unknown')}")
|
||||||
|
|
||||||
|
def on_tool_end(self, output: str, **kwargs: Any) -> None:
|
||||||
|
"""Called when a tool finishes executing."""
|
||||||
|
print(f"[AgentLens] Tool completed")
|
||||||
|
|
||||||
|
def on_tool_error(self, error: Exception, **kwargs: Any) -> None:
|
||||||
|
"""Called when a tool encounters an error."""
|
||||||
|
print(f"[AgentLens] Tool error: {error}")
|
||||||
|
|
||||||
|
def on_agent_action(self, action: Any, **kwargs: Any) -> None:
|
||||||
|
"""Called when an agent performs an action."""
|
||||||
|
print(f"[AgentLens] Agent action: {action.tool}")
|
||||||
39
packages/sdk-python/agentlens/integrations/openai.py
Normal file
39
packages/sdk-python/agentlens/integrations/openai.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"""OpenAI integration for AgentLens."""
|
||||||
|
|
||||||
|
from typing import Any, Optional
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_openai(client: Any) -> Any:
|
||||||
|
"""Wrap an OpenAI client to add AgentLens tracing.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client: The OpenAI client to wrap.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Wrapped OpenAI client with AgentLens tracing enabled.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
import openai
|
||||||
|
from agentlens.integrations.openai import wrap_openai
|
||||||
|
|
||||||
|
client = openai.OpenAI(api_key="sk-...")
|
||||||
|
traced_client = wrap_openai(client)
|
||||||
|
|
||||||
|
response = traced_client.chat.completions.create(...)
|
||||||
|
"""
|
||||||
|
original_create = client.chat.completions.create
|
||||||
|
|
||||||
|
@wraps(original_create)
|
||||||
|
def traced_create(*args: Any, **kwargs: Any) -> Any:
|
||||||
|
print("[AgentLens] OpenAI chat completion started")
|
||||||
|
try:
|
||||||
|
response = original_create(*args, **kwargs)
|
||||||
|
print("[AgentLens] OpenAI chat completion completed")
|
||||||
|
return response
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[AgentLens] OpenAI error: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
client.chat.completions.create = traced_create
|
||||||
|
return client
|
||||||
76
packages/sdk-python/agentlens/trace.py
Normal file
76
packages/sdk-python/agentlens/trace.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
"""Trace decorator and context manager for instrumenting agent functions."""
|
||||||
|
|
||||||
|
from typing import Callable, Optional, Any
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
|
||||||
|
def trace(name: Optional[str] = None) -> Callable[..., Any]:
|
||||||
|
"""Decorator to trace a function or method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name for the trace. If not provided, uses the function name.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Decorated function with tracing enabled.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
@trace(name="research-agent")
|
||||||
|
async def research(topic: str) -> str:
|
||||||
|
return f"Researching: {topic}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||||
|
@wraps(func)
|
||||||
|
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||||
|
trace_name = name or func.__name__
|
||||||
|
print(f"[AgentLens] Starting trace: {trace_name}")
|
||||||
|
try:
|
||||||
|
result = await func(*args, **kwargs)
|
||||||
|
print(f"[AgentLens] Completed trace: {trace_name}")
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[AgentLens] Error in trace {trace_name}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||||
|
trace_name = name or func.__name__
|
||||||
|
print(f"[AgentLens] Starting trace: {trace_name}")
|
||||||
|
try:
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
print(f"[AgentLens] Completed trace: {trace_name}")
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[AgentLens] Error in trace {trace_name}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
if hasattr(func, "__await__"):
|
||||||
|
return async_wrapper
|
||||||
|
else:
|
||||||
|
return sync_wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
class Tracer:
|
||||||
|
"""Context manager for creating traces.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
with Tracer(name="custom-operation"):
|
||||||
|
# Your code here
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __enter__(self) -> "Tracer":
|
||||||
|
print(f"[AgentLens] Starting trace: {self.name}")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:
|
||||||
|
if exc_type is None:
|
||||||
|
print(f"[AgentLens] Completed trace: {self.name}")
|
||||||
|
else:
|
||||||
|
print(f"[AgentLens] Error in trace {self.name}: {exc_val}")
|
||||||
|
return False
|
||||||
38
packages/sdk-python/agentlens/transport.py
Normal file
38
packages/sdk-python/agentlens/transport.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
"""Batch transport for sending data to AgentLens API."""
|
||||||
|
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
class BatchTransport:
|
||||||
|
"""Transport layer that batches events for efficient API calls.
|
||||||
|
|
||||||
|
This class handles batching and sending of traces, decisions, and other
|
||||||
|
events to the AgentLens backend.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, max_batch_size: int = 100, flush_interval: float = 1.0) -> None:
|
||||||
|
self.max_batch_size = max_batch_size
|
||||||
|
self.flush_interval = flush_interval
|
||||||
|
self._batch: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
def add(self, event: Dict[str, Any]) -> None:
|
||||||
|
"""Add an event to the batch.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event: Event data to be sent.
|
||||||
|
"""
|
||||||
|
self._batch.append(event)
|
||||||
|
if len(self._batch) >= self.max_batch_size:
|
||||||
|
self.flush()
|
||||||
|
|
||||||
|
def flush(self) -> None:
|
||||||
|
"""Flush the batch by sending all pending events."""
|
||||||
|
if not self._batch:
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"[AgentLens] Flushing batch of {len(self._batch)} events")
|
||||||
|
self._batch.clear()
|
||||||
|
|
||||||
|
def shutdown(self) -> None:
|
||||||
|
"""Shutdown the transport, flushing any remaining events."""
|
||||||
|
self.flush()
|
||||||
33
packages/sdk-python/pyproject.toml
Normal file
33
packages/sdk-python/pyproject.toml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.backends"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "agentlens"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Agent observability that traces decisions, not just API calls"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "MIT"
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
authors = [{ name = "Vectry", email = "hunter@repi.fun" }]
|
||||||
|
keywords = ["ai", "agents", "observability", "tracing", "llm"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Topic :: Software Development :: Libraries",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"httpx>=0.25.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
langchain = ["langchain-core>=0.1.0"]
|
||||||
|
openai = ["openai>=1.0.0"]
|
||||||
|
all = ["agentlens[langchain,openai]"]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://agentlens.vectry.tech"
|
||||||
|
Repository = "https://gitea.repi.fun/repi/agentlens"
|
||||||
|
Documentation = "https://agentlens.vectry.tech/docs"
|
||||||
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
}
|
||||||
17
turbo.json
Normal file
17
turbo.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://turbo.build/schema.json",
|
||||||
|
"globalDependencies": ["**/.env.*local"],
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
||||||
|
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
|
||||||
|
},
|
||||||
|
"dev": { "cache": false, "persistent": true },
|
||||||
|
"lint": {},
|
||||||
|
"clean": { "cache": false },
|
||||||
|
"db:generate": { "cache": false },
|
||||||
|
"db:migrate": { "cache": false },
|
||||||
|
"db:push": { "cache": false }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user