Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
YouTube Security VideosfreeCodeCamp.org: TimescaleDB Course – PostgreSQL for Time-Series Data(23.09.2026 um 12:30 Uhr)
Windows Tipps & SecurityAndroid 17: Rollout auf Samsung-Galaxy-Smartphones verzögert sich(23.09.2026 um 11:42 Uhr)
Unix & Linux ServerUSN-8733-2: Gzip vulnerabilities(22.09.2026 um 18:04 Uhr)
Sichere ProgrammierungHow to Build Custom PowerPoint Add-Ins for Enterprise Teams(23.09.2026 um 11:25 Uhr)
Sichere ProgrammierungSearch Google Jobs in Real-Time with Go and SerpApi 🚀(23.09.2026 um 12:13 Uhr)
Sichere ProgrammierungA Psychological State is a Coefficient Vector(23.09.2026 um 12:16 Uhr)
YouTube Security VideosfreeCodeCamp.org: TimescaleDB Course – PostgreSQL for Time-Series Data(23.09.2026 um 12:30 Uhr)
Windows Tipps & SecurityAndroid 17: Rollout auf Samsung-Galaxy-Smartphones verzögert sich(23.09.2026 um 11:42 Uhr)
Unix & Linux ServerUSN-8733-2: Gzip vulnerabilities(22.09.2026 um 18:04 Uhr)
Sichere ProgrammierungHow to Build Custom PowerPoint Add-Ins for Enterprise Teams(23.09.2026 um 11:25 Uhr)
Sichere ProgrammierungSearch Google Jobs in Real-Time with Go and SerpApi 🚀(23.09.2026 um 12:13 Uhr)
Sichere ProgrammierungA Psychological State is a Coefficient Vector(23.09.2026 um 12:16 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

I Built an AI Agent That Handles Orders, Refunds & Support Without LangChain

Why I skipped LangChain Every AI agent tutorial I found did one of two things: Either it used LangChain, which abstracts away the exact thing you need to understand or it was so simple it was basically a chatbot with if/else routing and…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!




Why I skipped LangChain



Every AI agent tutorial I found did one of two things:



Either it used LangChain, which abstracts away the exact thing you need to understand or it was so simple it was basically a chatbot with if/else routing and called itself an "agent."



I wanted to understand what an agent actually is at the code level. So I built one from scratch.



Turns out the whole thing is a while loop.




typescriptwhile (true) {
const response = await llm(messages);
if (noToolCalls) break; // Claude answered — we're done
await runTools(toolCalls); // Claude needs data — run the tools
messages.push(toolResults); // feed results back, loop again
}







That's the agent. Everything else is just well-written tools hanging off it.



What we built



An ecommerce support agent that can:



🔍 Search products by natural language query

📦 Check order status by order ID

📋 Answer return policy questions

🎫 Create support tickets (write action, foreshadows the Command pattern)



The agent figures out on its own which tool to call — sometimes multiple tools in a single message. You never write if (message.includes("order")) anywhere.



How tool-calling actually works



This is the part most tutorials gloss over.



When you pass tools to anthropic.messages.create(), Claude's response isn't just text but it's an array of content blocks, each tagged with a type:




{
"content": [
{ "type": "text", "text": "Let me check that for you." },
{
"type": "tool_use",
"id": "toolu_01A2bC...",
"name": "search_products",
"input": { "query": "wireless earbuds" }
}
],
"stop_reason": "tool_use"
}






Your code filters on type:




typescriptconst toolCalls = response.content.filter(
(block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
);






Notice the TypeScript type guard — (block): block is Anthropic.ToolUseBlock. This isn't just a runtime check. It tells TypeScript to narrow the type so call.name and call.input are properly typed, not any. The SDK exports ContentBlock = TextBlock | ToolUseBlock as a proper discriminated union — use it.



If toolCalls.length === 0, Claude responded with plain text. That's your exit condition. Loop ends.



Folder structure — organized to scale




src/

├── agent/

│ └── EcommerceAgent.ts # the while(true) loop as a class

├── tools/

│ ├── types.ts # shared Tool type

│ ├── index.ts # registry — the only file that knows all tools

│ ├── searchProducts.ts

│ ├── getOrderStatus.ts

│ └── getReturnPolicy.ts

├── data/

│ ├── products.ts # swap this for Postgres later

│ ├── orders.ts

│ └── policy.ts

└── cli.ts

The key decision: each tool in its own file. Adding a new tool means one new file + one line in tools/index.ts. The agent loop never changes.





typescript// tools/index.ts — the registry
export const tools: Tool[] = [
searchProductsTool,
getOrderStatusTool,
getReturnPolicyTool,
];









typescript// agent loop — never touches individual tools directly
const tool = this.toolset.find((t) => t.name === call.name);







This is the Factory pattern in practice — the registry decides which tool to instantiate, the agent just calls it by name.



The design patterns hiding in plain sight



I didn't set out to implement design patterns. But when you structure this properly they appear naturally:



Strategy — swappable LLM provider





typescriptclass EcommerceAgent {
constructor(
private readonly client: Anthropic = new Anthropic(),
private readonly toolset: Tool[] = tools,
private readonly model: string = "claude-sonnet-4-6"
) {}
}






Want to swap Anthropic for OpenAI? Change one constructor argument. Nothing else moves.



Factory, tool registry



The tools/index.ts file is your factory. The agent never does new SearchProductsTool() — it looks up by name. Adding a tool is additive, not a modification.



Repository (sort of) — data isolation



typescript// src/data/products.ts

export const products: Product[] = [ ... ];



Today it's an array. In production it becomes a Postgres query. The tool files that import from data/ don't change — only the data file itself changes. That's the Repository pattern's whole point.



Command — write actions need special treatment



getOrderStatus is a read. getReturnPolicy is a read. But createSupportTicket mutates something. In production that needs:



Audit logging

Confirmation before execution

Idempotency (don't create two tickets for one click)



That's the Command pattern — wrap write actions as objects with their own validation and logging, not just another function in the tool registry.



CQRS, already naturally split



Your read tools (search, status, policy) hit one data source. Your write tools (create ticket) hit another path entirely. The split is already there — CQRS just makes it intentional and explicit.



The one question everyone gets wrong



"Does this need WebSockets or SSE?"



No. Here's why.



The agent loop runs entirely server-side. Multiple Anthropic API calls, tool executions, result feeding — all of it happens inside one async function. From the client's perspective:



Client sends ONE request

→ server does the whole while(true) loop internally

→ server sends ONE response back



SSE is a UX upgrade (stream tokens as they arrive so users don't stare at a blank screen for 3 seconds), not a technical requirement. The agent works perfectly fine as a standard request/response without it.



What "static data" means for production



In the video we use hardcoded arrays:





typescript// today
const products = [ { id: "p1", title: "Wireless Earbuds Pro", ... } ];

// production
class PostgresProductRepository implements ProductRepository {
async search(query: string) {
const embedding = await embedText(query);
return vectorDb.query({ vector: embedding, topK: 5 });
}
}






The tool file doesn't change. The import changes. That's it.

That's what "basic but scalable" actually means — not that the simple version is production-ready, but that the seam where you'd upgrade is visible and clean.



Full agent loop, the complete picture




typescriptexport class EcommerceAgent {
constructor(
private readonly client: Anthropic = new Anthropic(),
private readonly toolset: Tool[] = tools,
private readonly model: string = "claude-sonnet-4-6"
) {}

async chat(userMessage: string, history: ChatMessage[] = []): Promise<ChatResult> {
const messages = [...history, { role: "user", content: userMessage }];

while (true) {
const response = await this.client.messages.create({
model: this.model,
max_tokens: 1024,
system: SYSTEM_PROMPT,
messages,
tools: this.toolset.map(({ execute, ...schema }) => schema),
});

const toolCalls = response.content.filter(
(block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
);

if (toolCalls.length === 0) {
const textBlock = response.content.find(
(block): block is Anthropic.TextBlock => block.type === "text"
);
return { reply: textBlock?.text ?? "", messages };
}

messages.push({ role: "assistant", content: response.content });
messages.push({ role: "user", content: await this.runTools(toolCalls) });
}
}

private async runTool(call: Anthropic.ToolUseBlock) {
const tool = this.toolset.find((t) => t.name === call.name);
if (!tool) {
return {
type: "tool_result",
tool_use_id: call.id,
content: JSON.stringify({ error: `Unknown tool: ${call.name}` }),
is_error: true,
};
}
try {
const result = await tool.execute(call.input);
return { type: "tool_result", tool_use_id: call.id, content: JSON.stringify(result) };
} catch (err) {
const message = err instanceof Error ? err.message : "Tool execution failed";
return {
type: "tool_result",
tool_use_id: call.id,
content: JSON.stringify({ error: message }),
is_error: true,
};
}
}

private async runTools(toolCalls: Anthropic.ToolUseBlock[]) {
return Promise.all(toolCalls.map((call) => this.runTool(call)));
}
}






Stack



Runtime: Node.js + tsx (no build step needed)

LLM: Anthropic SDK — claude-sonnet-4-6

Language: TypeScript (strict mode)

Data: In-memory arrays (production: Postgres + pgvector)



What's next is the production version



This video covers the agent layer. The full production version adds:



(Udemy)

Postgres + pgvector — real semantic product search with embeddings

Redis — conversation history that survives server restarts

Repository pattern — proper data abstraction layer

Command pattern — write actions with audit trails

CQRS — explicit read/write split

RAG pipeline — chunk and embed policy docs for real retrieval



Resources



📂 Full source code: need to add

🎥 YouTube video: https://youtu.be/rxPrtcl42to

📖 Anthropic tool-calling docs: https://docs.anthropic.com/en/docs/tool-use

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten I Built an AI Agent That Handles Orders, Refunds & Support Without LangChain

Thematisch verwandte Begriffe: Built, Agent, That, Handles · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-96258 | A vulnerability has been found in onSite internet GmbH Auktion NG Auktio…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick