Docs/Guides/First Agent

Monitor Your First Agent

Step-by-step guide to instrument your first AI agent with AgenticAnts in under 10 minutes.

Prerequisites

Before you begin, make sure you have:

  • AgenticAnts account - Sign up at agenticants.ai
  • API keys - Get your public/secret key pair (pk-ap-... / sk-ap-...) from the dashboard
  • Python 3.8+, Node.js 20+ (the JS tracing packages require Node 20+), or Java 17+
  • Basic knowledge of AI frameworks (LangChain, OpenAI, etc.)

Step 1: Install AgenticAnts SDK

Python Installation

bash
pip install ants-platform

Node.js Installation

bash
npm install ants-platform # For the LangChain example below, also install: npm install @antsplatform/langchain langchain @langchain/openai

The root ants-platform package re-exports the client plus the OpenTelemetry-based tracing helpers. Framework integrations live in scoped packages (@antsplatform/langchain, @antsplatform/openai).

Java Installation

Add the dependency to your pom.xml:

xml
<dependency> <groupId>ai.agenticants</groupId> <artifactId>ants-platform-java</artifactId> <version>0.2.0</version> </dependency>

Gradle: implementation("ai.agenticants:ants-platform-java:0.2.0"). Requires Java 17+.

Step 2: Initialize AgenticAnts

Python Setup

python
from ants_platform import AntsPlatform # Initialize AgenticAnts (this also configures the global singleton) ants = AntsPlatform( public_key=os.getenv("ANTS_PLATFORM_PUBLIC_KEY"), # "pk-ap-..." secret_key=os.getenv("ANTS_PLATFORM_SECRET_KEY"), # "sk-ap-..." host="https://api.agenticants.ai", ) print("AgenticAnts initialized successfully!")

Node.js Setup

Tracing in the JS SDK is OpenTelemetry-based. Register the AntsPlatformSpanProcessor once at startup (before the rest of your app runs) — this is what actually ships spans to AgenticAnts. Put this in an instrumentation.ts you import first.

typescript
// instrumentation.ts const provider = new NodeTracerProvider({ spanProcessors: [ new AntsPlatformSpanProcessor({ publicKey: process.env.ANTS_PLATFORM_PUBLIC_KEY, // "pk-ap-..." secretKey: process.env.ANTS_PLATFORM_SECRET_KEY, // "sk-ap-..." baseUrl: 'https://api.agenticants.ai', }), ], }) provider.register() setAntsPlatformTracerProvider(provider) // so observe()/CallbackHandler use this provider console.log('AgenticAnts tracing initialized successfully!')

Load it before your app code, e.g. node --import ./instrumentation.js app.js, or import './instrumentation' at the very top of your entry file.

The REST client (new AntsPlatformClient({ publicKey, secretKey, baseUrl })) is a separate object used for prompts, datasets, and scores — it does not capture traces. Trace capture is the span processor above.

Java Setup

Tracing in the Java SDK is OpenTelemetry-based. Call AntsPlatformOtel.configure(...) once at startup (before any instrumented code runs) — this registers the global OpenTelemetry tracer that all instrumentation uses. The host defaults to https://api.agenticants.ai, so you only call .baseUrl(...) to override it.

java
public class App { public static void main(String[] args) { AntsPlatformOtel.configure(AntsPlatformOtel.options() .publicKey(System.getenv("ANTS_PLATFORM_PUBLIC_KEY")) // "pk-ap-..." .secretKey(System.getenv("ANTS_PLATFORM_SECRET_KEY")) // "sk-ap-..." .environment("production")); // optional System.out.println("AgenticAnts tracing initialized successfully!"); } }

Flush before a short-lived process exits with AntsPlatformOtel.flush() (or AntsPlatformOtel.shutdown()) so batched spans are delivered.

Step 3: Create Your First Agent

A real agent is more than one LLM call — it plans, calls a tool, and reaches a dependent service. We'll build a small currency assistant and instrument it end-to-end so the dashboard shows the full tree: a root agent span, with a nested generation (LLM call with model + token usage), a tool call, and a span for the downstream HTTP service. The backend computes cost server-side from the model + usageDetails/usage_details you report on the generation.

Python Example

start_as_current_observation opens a context-managed observation; anything opened inside it nests automatically. Note the two Python rules: always set host explicitly (Step 2), and always pass agent_name= when as_type="agent".

python
from ants_platform import get_client client = get_client() def currency_agent(question: str, amount: float, base: str, quote: str) -> str: # Root agent span — as_type="agent" REQUIRES agent_name= with client.start_as_current_observation( name="currency-agent", as_type="agent", agent_name="currency-agent", ): client.update_current_trace( name="currency-agent", input=question, user_id="demo-user", session_id="demo-session", tags=["currency", "tutorial"], ) # 1. LLM generation — model + usage_details let the backend compute cost prompt = f"Plan how to answer: {question}" with client.start_as_current_observation( name="plan", as_type="generation", model="gpt-4o-mini", input=prompt, model_parameters={"temperature": 0.2}, ) as gen: plan = f"Convert {amount} {base} to {quote} using the live rate." gen.update( output=plan, usage_details={"input": 48, "output": 12, "total": 60}, ) # 2. Dependent service — a downstream HTTP call as its own span with client.start_as_current_observation( name="fetch-rate", as_type="span", input={"base": base, "quote": quote}, ) as span: resp = httpx.get(f"https://api.frankfurter.app/latest?from={base}&to={quote}") rate = resp.json()["rates"][quote] span.update(output={"rate": rate}) # 3. Tool call — deterministic conversion with client.start_as_current_observation( name="currency-convert", as_type="tool", input={"amount": amount, "rate": rate}, ) as tool: converted = round(amount * rate, 2) tool.update(output={quote: converted}) answer = f"{amount} {base} = {converted} {quote} (rate {rate})" client.update_current_trace(output={"answer": answer}) return answer # Test your agent answer = currency_agent("How much is 100 USD in EUR?", 100, "USD", "EUR") print(f"Agent response: {answer}") # Flush buffered spans before the process exits (short-lived scripts) client.flush()

Node.js Example

startActiveObservation runs its callback with the observation as the active context, so children nest under it. Every generation should carry model and usageDetails — that's what the backend uses to compute cost.

typescript
startActiveObservation, startObservation, updateActiveTrace, getActiveTraceId, } from 'ants-platform' async function currencyAgent( question: string, amount: number, base: string, quote: string, ) { return startActiveObservation( 'currency-agent', async () => { updateActiveTrace({ name: 'currency-agent', input: question, userId: 'demo-user', sessionId: 'demo-session', tags: ['currency', 'tutorial'], }) // 1. LLM generation — model + usageDetails drive server-side cost const prompt = `Plan how to answer: ${question}` const gen = startObservation( 'plan', { model: 'gpt-4o-mini', input: prompt, modelParameters: { temperature: 0.2 } }, { asType: 'generation' }, ) const plan = `Convert ${amount} ${base} to ${quote} using the live rate.` gen.update({ output: plan, usageDetails: { input: 48, output: 12, total: 60 } }) gen.end() // 2. Dependent service — a downstream HTTP call as its own span const rate = await startActiveObservation( 'fetch-rate', async (span) => { span.update({ input: { base, quote } }) const res = await fetch( `https://api.frankfurter.app/latest?from=${base}&to=${quote}`, ) const data = await res.json() span.update({ output: { rate: data.rates[quote] } }) return data.rates[quote] as number }, { asType: 'span' }, ) // 3. Tool call — deterministic conversion const converted = Math.round(amount * rate * 100) / 100 const tool = startObservation( 'currency-convert', { input: { amount, rate } }, { asType: 'tool' }, ) tool.update({ output: { [quote]: converted } }) tool.end() const answer = `${amount} ${base} = ${converted} ${quote} (rate ${rate})` updateActiveTrace({ output: answer }) return { answer, traceId: getActiveTraceId() } }, { asType: 'agent' }, ) } // Test your agent const { answer } = await currencyAgent('How much is 100 USD in EUR?', 100, 'USD', 'EUR') console.log(`Agent response: ${answer}`)

Prefer to let a framework emit the spans? Wrap the OpenAI client once with observeOpenAI from @antsplatform/openai (every call is auto-traced as a generation), or pass a LangChain CallbackHandler per invocation:

typescript
import './instrumentation' import OpenAI from 'openai' import { observeOpenAI } from '@antsplatform/openai' import { CallbackHandler } from '@antsplatform/langchain' const openai = observeOpenAI(new OpenAI()) // every call auto-traced const handler = new CallbackHandler({ sessionId: 'demo-session', userId: 'demo-user', tags: ['currency'] }) // await chain.invoke(input, { callbacks: [handler] })

Java Example

Ants.observe(name, type, fn) runs its lambda with the observation as the active context, so anything opened inside it nests automatically. Every generation should carry model and usage — that's what the backend uses to compute cost. Unlike Python, Java does not need an agent name: for ObservationType.AGENT the SDK auto-derives the agent name from the observation name (no agent_name= trap).

java
public class CurrencyAgent { public static void main(String[] args) { AntsPlatformOtel.configure(AntsPlatformOtel.options() .publicKey(System.getenv("ANTS_PLATFORM_PUBLIC_KEY")) .secretKey(System.getenv("ANTS_PLATFORM_SECRET_KEY"))); String answer = Ants.observe("currency-agent", ObservationType.AGENT, agent -> { agent.updateTrace(TraceAttributes.builder() .name("currency-agent").userId("demo-user").sessionId("demo-session") .tags(List.of("currency", "tutorial")) .input("How much is 100 USD in EUR?").build()); // 1. LLM generation — model + usage let the backend compute cost AntsGeneration gen = Ants.startGeneration("plan") .model("gpt-4o-mini").modelParameters(Map.of("temperature", 0.2)) .input("Plan how to answer the question"); gen.output("Convert 100 USD to EUR using the live rate.").usage(48, 12).end(); // 2. Dependent service — a downstream call as its own span double rate = Ants.observe("fetch-rate", ObservationType.SPAN, span -> { span.input(Map.of("base", "USD", "quote", "EUR")); double r = 0.92; // call your FX service here span.output(Map.of("rate", r)); return r; }); // 3. Tool call — deterministic conversion double converted = Math.round(100 * rate * 100.0) / 100.0; AntsObservation tool = Ants.startObservation("currency-convert", ObservationType.TOOL); tool.input(Map.of("amount", 100, "rate", rate)).output(Map.of("EUR", converted)).end(); String result = "100 USD = " + converted + " EUR"; agent.updateTrace(TraceAttributes.builder().output(result).build()); return result; }); System.out.println("Agent response: " + answer); AntsPlatformOtel.flush(); // deliver buffered spans before exit } }

Step 4: View Results in Dashboard

  1. Open AgenticAnts Dashboard - Go to app.agenticants.ai

  2. View Traces - Click on "Traces" to see your agent interactions

  3. Analyze Performance - Check metrics like:

    • Response time
    • Token usage
    • Success rate
    • Error rate
  4. Monitor Costs - View cost breakdown by:

    • Model used
    • Token consumption
    • Time period

Step 5: Add More Monitoring

Track User Interactions

python
from ants_platform import observe, get_client @observe(name="customer-support") def enhanced_customer_support_agent(question: str, user_id: str, session_id: str): client = get_client() # Annotate the active trace with user/session context client.update_current_trace( name="customer-support", input=question, user_id=user_id, session_id=session_id, metadata={"agent_type": "customer_support"}, ) # A retriever step — pull relevant docs before answering with client.start_as_current_observation( name="retrieve-docs", as_type="retriever", input={"query": question}, ) as retriever: docs = ["reset-password.md", "account-recovery.md"] retriever.update(output={"docs": docs}) # The LLM generation — model + usage_details so the backend computes cost with client.start_as_current_observation( name="response_generation", as_type="generation", model="gpt-4o-mini", input={"question": question, "context": docs}, model_parameters={"temperature": 0.7}, ) as gen: response = f"Here's how to reset your password, based on {len(docs)} docs." gen.update( output=response, usage_details={"input": 220, "output": 90, "total": 310}, ) # Record the final result on the trace this function opened client.update_current_trace( output=response, metadata={"success": True}, ) return response

In Java, wrap the unit of work with Ants.observe(...) and open a RETRIEVER observation before the generation. There is no @observe annotation in Java — use Ants.observe(name, type, fn) instead.

java
public String enhancedCustomerSupportAgent(String question, String userId, String sessionId) { return Ants.observe("customer-support", ObservationType.AGENT, agent -> { // Annotate the active trace with user/session context agent.updateTrace(TraceAttributes.builder() .name("customer-support").input(question) .userId(userId).sessionId(sessionId) .metadata(Map.of("agent_type", "customer_support")).build()); // A retriever step — pull relevant docs before answering List<String> docs = Ants.observe("retrieve-docs", ObservationType.RETRIEVER, retriever -> { retriever.input(Map.of("query", question)); List<String> found = List.of("reset-password.md", "account-recovery.md"); retriever.output(Map.of("docs", found)); return found; }); // The LLM generation — model + usage so the backend computes cost AntsGeneration gen = Ants.startGeneration("response_generation") .model("gpt-4o-mini").modelParameters(Map.of("temperature", 0.7)) .input(Map.of("question", question, "context", docs)); String response = "Here's how to reset your password, based on " + docs.size() + " docs."; gen.output(response).usage(220, 90).end(); // Record the final result on the trace this observation opened agent.updateTrace(TraceAttributes.builder() .output(response).metadata(Map.of("success", true)).build()); return response; }); }

Add Error Handling

Wrap any function with observe() to capture it as a span — including its inputs, output, and errors. The span is recorded on the active trace automatically.

typescript
const robustCustomerSupportAgent = observe( async (question: string, userId: string) => { // Each step is its own span; on failure mark level: "ERROR" + statusMessage const validate = startObservation('validate-input', { input: { question } }, { asType: 'span' }) try { if (!question || question.length < 3) throw new Error('Question too short') validate.update({ output: { ok: true } }) } catch (e) { validate.update({ level: 'ERROR', statusMessage: String(e) }) throw e } finally { validate.end() } const gen = startObservation( 'answer', { model: 'gpt-4o-mini', input: question, modelParameters: { temperature: 0.7 } }, { asType: 'generation' }, ) try { const response = await currencyAgent(question, 100, 'USD', 'EUR') gen.update({ output: response.answer, usageDetails: { input: 60, output: 24, total: 84 } }) return response.answer } catch (error) { // Record the failure on the span, then return a graceful fallback gen.update({ level: 'ERROR', statusMessage: String(error) }) return "I'm sorry, I encountered an issue. Please try again or contact support." } finally { gen.end() } }, { name: 'customer-support' }, )

In Java, wrap the work with Ants.observe(...) and, on failure, mark the span with .level("ERROR").statusMessage(...) before ending it (or call obs.recordError(e), which records the exception and sets the ERROR status for you).

java
public String robustCustomerSupportAgent(String question) { return Ants.observe("customer-support", ObservationType.AGENT, agent -> { // Each step is its own span; on failure mark level "ERROR" + statusMessage AntsObservation validate = Ants.startObservation("validate-input", ObservationType.SPAN); validate.input(Map.of("question", question)); try { if (question == null || question.length() < 3) { throw new IllegalArgumentException("Question too short"); } validate.output(Map.of("ok", true)); } catch (Exception e) { validate.level("ERROR").statusMessage(e.getMessage()); validate.end(); throw e; } validate.end(); AntsGeneration gen = Ants.startGeneration("answer") .model("gpt-4o-mini").modelParameters(Map.of("temperature", 0.7)) .input(question); try { String response = "100 USD = 92.0 EUR"; // call your model here gen.output(response).usage(60, 24).end(); return response; } catch (Exception e) { // Record the failure on the span, then return a graceful fallback gen.level("ERROR").statusMessage(e.getMessage()); gen.end(); return "I'm sorry, I encountered an issue. Please try again or contact support."; } }); }

Step 6: Set Up Alerts

Create Performance Alerts

Alerts are a dashboard feature — there is no programmatic alerts API in the SDK. In app.agenticants.ai, open Alerts and create rules such as High Error Rate (error rate > 5% over 5m) or Slow Response Time (p95 latency > 3000ms over 10m), and pick email/Slack channels. Once your agent emits traces (above), the latency and error data these alerts watch is populated automatically.

Create Cost Alerts

Alerts are configured in the dashboard, not via the SDK. In app.agenticants.ai, open Alerts and create rules such as High Daily Cost or Monthly Budget with email/Slack channels. Once your agent is emitting traces (above), the cost and latency data these alerts watch is populated automatically.

Step 7: Monitor and Optimize

View Key Metrics

There is no SDK metrics-query API. Total requests, average response time, error rate, total cost, and success rate for the customer-support agent are available in the Dashboard and Traces views at app.agenticants.ai, filterable by agent, model, and time range (e.g. last 7 days). These views are driven directly by the traces your agent emits.

Request volume, latency percentiles, token usage, and cost trends are available in the Dashboard and Traces views at app.agenticants.ai, filterable by agent, model, and time range. There is no SDK metrics-query API in the JS SDK — the dashboards are driven directly by the traces your agent emits.

Common Issues and Solutions

Issue: "API Key not found"

Solution: Make sure both keys (and the base URL) are set in environment variables:

bash
export ANTS_PLATFORM_PUBLIC_KEY="pk-ap-..." export ANTS_PLATFORM_SECRET_KEY="sk-ap-..." export ANTS_PLATFORM_BASE_URL="https://api.agenticants.ai"

Issue: "No traces appearing in dashboard"

Solution (JS): Confirm your instrumentation file is imported before any instrumented code runs, that every span is ended (observe() does this for you), and that spans are flushed before the process exits. For short-lived scripts, call await provider.forceFlush() (or provider.shutdown()) before exiting so batched spans are sent.

Solution (Java): Ensure AntsPlatformOtel.configure(...) runs before any instrumented code, and call AntsPlatformOtel.flush() (or AntsPlatformOtel.shutdown()) before a short-lived process exits so batched spans are delivered.

Issue: "High latency"

Solution:

  • Check your network connection
  • Consider using a different model
  • Implement caching for repeated queries

Issue: "High costs"

Solution:

  • Use smaller models for simple tasks
  • Implement response caching
  • Optimize prompts to reduce token usage

Next Steps

Now that you have your first agent monitored, here's what to explore next:

  1. Advanced Monitoring - Learn about multi-agent systems
  2. Cost Optimization - Reduce costs with our optimization guide
  3. Production Deployment - Deploy to production with best practices
  4. Integration Guides - Connect with LangChain or other frameworks

Example Projects

Check out these complete examples:

Support

Need help? We're here for you:


Congratulations! 🎉 You've successfully instrumented your first AI agent with AgenticAnts. You now have complete visibility into your agent's performance, costs, and behavior.

© 2026 ANTS Platform, Inc.Docs v1.0 · Last updated June 2026