MJH
Portfolio
Blog

Marouane El Moujahid

Full Stack Developer & Software Engineering Student based in Morocco, specialized in building modern web applications, backend systems, and DevOps solutions using React, NestJS, and Docker.

Quick Links

  • About Me
  • Projects
  • Blog
  • Resume

Connect

Keyboard Shortcuts

⌘K
Search
T
Toggle theme
2026 Marouane ElMoujahid. All rights reserved.
Built withusing Next.js & Tailwind
HomeBlogReAct Agent for Conversational Commerce in ASP.NET Core
Software EngineeringLLMAI

ReAct Agent for Conversational Commerce in ASP.NET Core

October 18, 2026
8 min read
0 comments
On this page
  1. What is a ReAct agent?
  2. Why conversational commerce needs more than RAG
  3. Architecture in ASP.NET Core
  4. Step 1: route by intent before spending tokens
  5. Step 2: define tools with strict schemas
  6. Step 3: run the loop with a hard step limit
  7. A real request, step by step
  8. Step 4: keep the model honest
  9. Step 5: combine the agent with an expert system
  10. Common mistakes to avoid
  11. Frequently asked questions
  12. What is a ReAct agent in simple terms?
  13. Do I need RAG if I already have tools?
  14. How do I stop an agent from looping forever?
  15. Which LLM works best for tool calling?
  16. Can I build this without ASP.NET Core?
  17. Key takeaways

A ReAct agent for conversational commerce lets a shopper say "something fresh for summer, under 800 MAD, and add the best one to my cart" and get a correct result. The agent reasons about the request, calls real store functions, reads what they return, and only then answers. This post shows how we built one in ASP.NET Core 8 for ALOud, a perfume store, and which design decisions mattered most.

What is a ReAct agent?

ReAct stands for Reasoning + Acting. Instead of answering in one shot, the model alternates between three steps:

  1. Thought: decide what is needed next.
  2. Action: call a tool, such as search_products or add_to_cart.
  3. Observation: read the tool result and decide again.

Image

The loop ends when the model has enough facts to give a final answer. For a store this matters because prices, stock and cart contents change constantly. A plain chatbot has to guess them. A ReAct agent looks them up.

In practice we use the model's native function calling rather than parsing "Thought:" lines from text. The reasoning happens inside the model instead of being written out, but the loop is the same: call, observe, decide.

Why conversational commerce needs more than RAG

Retrieval-augmented generation (RAG) is great for knowledge questions: "What does oud smell like?" or "Which of these lasts longest?" ALOud indexes every perfume as text chunks, embeds them with Ollama, and stores the vectors in Qdrant. At question time we embed the query, retrieve the top five chunks, and let the LLM answer from that context.

RAG alone cannot do anything, though. It cannot add a bottle to the cart, and its index can be minutes or hours old. Conversational commerce needs both:

NeedBest tool
Product knowledge, notes, comparisonsRAG over Qdrant
Cart changes, live totals, stockAgent tools on live services
Personalised recommendationsRule-based expert system (NRules), called as an agent tool

Architecture in ASP.NET Core

Image

Everything is a normal scoped service in Program.cs, so the agent reuses the same code paths as the website:

text
builder.Services.AddScoped<IChatOrchestratorService, ChatOrchestratorService>(); // RAG
builder.Services.AddScoped<RagToolDispatcher>();                                  // Tools
builder.Services.AddScoped<RagCartService>();                                     // Agent loop
builder.Services.AddScoped<IRagLLMClient>(sp =>
    sp.GetRequiredService<LLMClientFactory>().CreateClient());                    // Gemini, Groq or Grok

The LLM provider is chosen with an environment variable (LLM_PROVIDER), so you can switch between Google's Gemini, Groq (a fast inference provider for open models) and xAI's Grok without touching the agent. Cart state lives in Redis behind ICartService, the same service the website uses. A controller exposes the agent at POST /api/v1/ai/cart.

Step 1: route by intent before spending tokens

Not every message needs an agent loop. A lightweight keyword-based classifier labels each message first, and pure knowledge questions go straight to RAG:

text
if (intent is UserIntent.Search)
    return await _ragAnswerService.AnswerAsync(userId, userMessage, new RagAnswerOptions { TopK = 5 });

Cart, recommendation and general requests enter the agent. Each intent also gets its own small tool list and short system prompt. Giving the model six cart tools instead of every tool cuts token usage and reduces wrong tool choices.

Step 2: define tools with strict schemas

A tool is a name, a description, and a JSON schema for its arguments. The schema is the contract the model must follow:

text
new RagToolDefinition
{
    Name = "add_to_cart",
    Description = "Add product to cart. Automatically checks stock.",
    ParametersSchema = new
    {
        type = "object",
        properties = new
        {
            productId = new { type = "string" },
            quantity  = new { type = "integer", minimum = 1, @default = 1 }
        },
        required = new[] { "productId" }
    }
}

Keep descriptions short and literal. Models follow "Add product to cart" more reliably than a paragraph of prose.

Step 3: run the loop with a hard step limit

The loop sends the conversation, executes any requested tool, appends the observation, and asks again:

text
for (var step = 0; step < MaxToolCalls; step++)
{
    var result = await _agentLlm.ExecuteAsync(new RagLLMRequest
    {
        SystemPrompt = systemPrompt,
        UserMessage = userMessage,
        Context = cartContext,              // cart loaded fresh at the start of the request
        ConversationHistory = history,
        Tools = tools
    });

    if (!result.IsToolCall)
        return new RagResponse { Answer = result.FinalAnswer ?? Fallback(intent) };

    history.Add(AssistantCall(result.ToolCall!));
    var observation = await _dispatcher.DispatchAsync(result.ToolCall!.Name, result.ToolCall.Arguments);
    history.Add(ToolResult(result.ToolCall.Name, observation));
}
return new RagResponse { Answer = Fallback(intent) }; // budget exhausted

The cart is read from Redis when the request starts, and every cart tool returns the updated cart in its observation. The model never works from a stale snapshot, even after it changes the cart mid-loop.

Two details keep the loop bounded:

  • MaxToolCalls is 10. A confused model cannot loop forever or burn your API budget.
  • A deterministic fallback. If the budget runs out, the user gets a helpful message, not a stack trace.

A real request, step by step

Here is the opening example, traced through the loop:

Image

text
User:        Something fresh for summer, under 800 MAD, and add the best one to my cart.

Action:      search_products { "query": "fresh summer fragrance", "maxPrice": 800 }
Observation: [ { "id": "p-102", "name": "Neroli Bloom", "price": 690, "inStock": true },
               { "id": "p-087", "name": "Citrus Veil", "price": 540, "inStock": true } ]

Action:      add_to_cart { "productId": "p-102", "quantity": 1 }
Observation: { "ok": true, "name": "Neroli Bloom", "quantity": 1, "cartTotal": 690 }

Answer:      I added Neroli Bloom (690 MAD) to your cart. It has bright neroli and
             citrus notes that stay light in hot weather. Your cart total is 690 MAD.

Two tool calls, two observations, one grounded answer. Nothing in the reply was guessed.

Step 4: keep the model honest

Hallucinated products are the biggest risk in AI commerce. We use three guardrails:

  • Ground every answer. The prompt says: use only the provided context, never invent products, prices or availability, and say "I don't know" when information is missing.
  • Validate in the dispatcher. The model proposes add_to_cart(productId), but the dispatcher loads the perfume from the database. If the ID does not exist, the tool returns { Ok = false, Error = "Perfume not found" } and the model recovers.
  • Return structured observations. Tools return small JSON objects (name, quantity, total), not free text, so the next reasoning step has clean facts.

Image

Provider errors get the same treatment: a rate limit from the LLM becomes a clear 429 response instead of a generic failure.

Step 5: combine the agent with an expert system

Recommendations should not depend on a language model's mood. ALOud uses NRules to score perfumes from a profile: climate, occasion, skin type, persona and sensitivities. Rules like "hot climate prefers fresh, light accords" are explicit, testable and explainable. The agent calls this engine as a tool and then explains the result in natural language.

Image

The model handles conversation, and the rules handle judgement.

Common mistakes to avoid

  • Letting the model own state. Cart state lives in Redis, behind ICartService. The model only requests changes.
  • Sending every tool every time. Fewer tools means better accuracy and lower cost.
  • Skipping identity. Our current prototype still uses a placeholder user ID in the controller. Wire the authenticated user in before launch, or carts will not persist per customer.
  • Matching intent with keywords only. Our keyword classifier is cheap and fine to start, but a small classifier or an LLM router handles phrasing you did not predict.
  • No tests. Test the dispatcher and the validation logic with a scripted fake LLM. You do not need a live model to prove the loop stops, recovers and falls back.

Frequently asked questions

What is a ReAct agent in simple terms?

A ReAct agent is an LLM that alternates between thinking, calling a tool, and reading the result until it can answer. It lets the model use live data and take actions instead of guessing from memory.

Do I need RAG if I already have tools?

Usually yes. Tools are best for live data and actions such as cart and stock. RAG is best for large amounts of descriptive text such as product notes and guides. Use each where it is strongest.

How do I stop an agent from looping forever?

Set a hard maximum number of steps, return a deterministic fallback when it is reached, and log every step. We cap ours at 10 tool calls per request.

Which LLM works best for tool calling?

Any model with reliable function calling works. Smaller models tend to invent tools, so give them a short, explicit tool list and test them. Keeping the provider behind an interface lets you swap models as better ones appear.

Can I build this without ASP.NET Core?

Yes, the pattern is framework-independent. ASP.NET Core is a good fit because dependency injection, background services and typed HTTP clients make the pieces easy to compose and test.

Key takeaways

  • A ReAct agent for conversational commerce combines reasoning, tools and live data in a bounded loop.
  • Use RAG for knowledge and tools for actions, and route between them by intent.
  • Validate every tool call server-side. The model proposes, your code decides.
  • Add step limits, fallbacks and tests before you add features.

Want to dig into the code? It is on GitHub. Try asking the assistant for "a fresh summer fragrance under 800 MAD" and watch it search, compare and fill your cart.

Marouane El Moujahid

Written by Marouane El Moujahid

Software engineering student at FST Mohammedia and full stack developer (React, NestJS, Docker). Open to PFE internships and freelance projects.

About meGet in touch

Keep reading

Software Engineering · Mar 5, 2026

What Happens When Your Server Loses Power Mid-Processing?

Building a crash-resilient storage engine for a custom message broker — from first principles.

Comments (0)

Leave a Comment

No comments yet. Be the first to comment!

On this page

  1. What is a ReAct agent?
  2. Why conversational commerce needs more than RAG
  3. Architecture in ASP.NET Core
  4. Step 1: route by intent before spending tokens
  5. Step 2: define tools with strict schemas
  6. Step 3: run the loop with a hard step limit
  7. A real request, step by step
  8. Step 4: keep the model honest
  9. Step 5: combine the agent with an expert system
  10. Common mistakes to avoid
  11. Frequently asked questions
  12. What is a ReAct agent in simple terms?
  13. Do I need RAG if I already have tools?
  14. How do I stop an agent from looping forever?
  15. Which LLM works best for tool calling?
  16. Can I build this without ASP.NET Core?
  17. Key takeaways