> ## Documentation Index
> Fetch the complete documentation index at: https://framework.beeai.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# The Grand Tour

> Take a guided tour through the BeeAI Framework, building agents from the basics to advanced capabilities

This step-by-step guide shows how to start with your first agent and progressively add tools, debugging, reasoning, knowledge, and more. Each section introduces a single capability, so you can follow the full path or jump to the parts most useful to you.

## Journey Overview

Here’s a quick map of the stages and modules:

<CardGroup cols={2}>
  <Card title="Foundation" icon="layer-group" href="#foundation">
    Your first chat agent using Agent, Backend, and Tool modules
  </Card>

  <Card title="Debugging" icon="eyes" href="#debugging">
    Add logging and monitoring to debug your agent's behavior
  </Card>

  <Card title="Requirements" icon="check" href="#requirements">
    Add reasoning rules, guardrails, and user permissions
  </Card>

  <Card title="Knowledge" icon="book-open" href="#knowledge">
    Ground your agent in data with RAG capabilities
  </Card>

  <Card title="Orchestration" icon="sitemap" href="#orchestration">
    Coordinate teams of specialized agents with Workflows
  </Card>

  <Card title="Production" icon="gears" href="#production">
    Scale with caching and error handling
  </Card>

  <Card title="Integration" icon="server" href="#integration">
    Expose agents as services (MCP, Agent Stack, A2A, IBM wxO)
  </Card>
</CardGroup>

## Before You Start

* **Python 3.11+**
* **BeeAI Framework**: `pip install 'beeai-framework[wikipedia]'`
* **Ollama** running locally: [Download Ollama](https://ollama.com/)
* **Model downloaded**: `ollama pull granite3.3`

<Tip>
  You can also use other LLM providers like OpenAI, Anthropic, or watsonx - see [Backend](../modules/backend) to learn more about supported providers.
</Tip>

***

## Foundation

### Your First Agent

<Info>
  **Relevant Modules**: [Agent](../modules/agents), [Backend](../modules/backend)
</Info>

Let's start with the simplest possible agent - one that can respond to messages.

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.backend import ChatModel

  async def main():
      agent = RequirementAgent(
          llm=ChatModel.from_name("ollama:granite3.3"),
          role="friendly AI assistant",
          instructions="Be helpful and conversational in all your interactions."
      )

      response = await agent.run("Hello! What can you help me with?")
      print(response.last_message.text)

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  import { RequirementAgent } from "beeai-framework/agents/requirement/agent";
  import { ChatModel } from "beeai-framework/backend/chat";

  const agent = new RequirementAgent({
    llm: await ChatModel.fromName("ollama:granite3.3"),
    role: "friendly AI assistant",
    instructions: "Be helpful and conversational in all your interactions.",
  });

  const response = await agent.run({ prompt: "Hello! What can you help me with?" });
  console.log(response.result.text);
  ```
</CodeGroup>

**Try it:**

1. Save as `simple_agent.py`
2. Run `python simple_agent.py`
3. Test different prompts

**Troubleshooting**

<AccordionGroup>
  <Accordion title="Ollama not responding?">
    Verify it's running: `ollama list`<br />
    Start the service: `ollama serve`
  </Accordion>

  <Accordion title="Model not found?">
    Pull the model: `ollama pull granite3.3`<br />
    List available models: `ollama list`<br />
    Create an alias: If your granite model doesn't have the name `granite3.3` give it the alias by trying this command in your terminal `ollama cp <existing model name> <alias>`
  </Accordion>

  <Accordion title="Import errors?">
    Update to the latest version: `pip install --upgrade beeai-framework`<br />
    Check Python version: `python --version` (must be >= 3.11)
  </Accordion>
</AccordionGroup>

### Add Real-World Knowledge

<Info>
  Related Module: [Tools](https://framework.beeai.dev/modules/tools)
</Info>

Give your agent the ability to access real-world information, external systems, or running code by adding tools.

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.backend import ChatModel
  from beeai_framework.tools.search.wikipedia import WikipediaTool
  from beeai_framework.tools.weather import OpenMeteoTool

  async def main():
      agent = RequirementAgent(
          llm=ChatModel.from_name("ollama:granite3.3"),
          role="friendly AI assistant",
          instructions="Be helpful and conversational in all your interactions. Use your tools to find accurate, current information.",
          tools=[WikipediaTool(), OpenMeteoTool()],
      )

      response = await agent.run("What's the current weather in New York and tell me about the history of the city?")
      print(response.last_message.text)

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  import { RequirementAgent } from "beeai-framework/agents/requirement/agent";
  import { ChatModel } from "beeai-framework/backend/chat";
  import { WikipediaTool } from "beeai-framework/tools/search/wikipedia";
  import { OpenMeteoTool } from "beeai-framework/tools/weather/openMeteo";

  const agent = new RequirementAgent({
    llm: await ChatModel.fromName("ollama:granite3.3"),
    role: "friendly AI assistant",
    instructions:
      "Be helpful and conversational in all your interactions. Use your tools to find accurate, current information.",
    tools: [new WikipediaTool(), new OpenMeteoTool()],
  });

  const response = await agent.run({
    prompt: "What's the current weather in New York and tell me about the history of the city?",
  });
  console.log(response.result.text);
  ```
</CodeGroup>

Try these prompts:

* "What's the weather in different cities around the world?"
* "Tell me about quantum computing and the current weather in CERN's location"
* "Compare the weather in New York and London, then tell me about their geographical similarity"

<Tip>
  Learn more about the [RequirementAgent](/modules/agents/requirement-agent), BeeAI's suggested agent implementation for reliability and control over agent behavior.
</Tip>

***

## Debugging

<Info>
  Related Modules: [Emitter](../modules/emitter), [Events](../modules/events), [Observability](../modules/observability).
</Info>

Knowing what your application is doing is essential from the very start.
The BeeAI Framework is based on the event system; each component in the framework emits events throughout its execution.
You can listen and alter these events to build custom logic.

### Framework Insights

The most simple way to see what's happening in your application is by using `GlobalTrajectoryMiddleware` which listens to all events and prints them to the console.

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.backend import ChatModel
  from beeai_framework.tools.weather import OpenMeteoTool
  from beeai_framework.middleware.trajectory import GlobalTrajectoryMiddleware
  from beeai_framework.tools import Tool

  async def main():
      agent = RequirementAgent(
          llm=ChatModel.from_name("ollama:granite3.3"),
          tools=[OpenMeteoTool()]
      )

      response = await agent.run("What's the current weather in Paris?").middleware(
          GlobalTrajectoryMiddleware(included=[Tool]))  # Only show tool executions

      print(response.last_message.text)

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  import { RequirementAgent } from "beeai-framework/agents/requirement/agent";
  import { ChatModel } from "beeai-framework/backend/chat";
  import { OpenMeteoTool } from "beeai-framework/tools/weather/openMeteo";
  import { GlobalTrajectoryMiddleware } from "beeai-framework/middleware/trajectory";
  import { Tool } from "beeai-framework/tools/base";

  const agent = new RequirementAgent({
    llm: await ChatModel.fromName("ollama:granite3.3"),
    tools: [new OpenMeteoTool()],
  });

  const response = await agent
    .run({ prompt: "What's the current weather in Paris?" })
    .middleware(new GlobalTrajectoryMiddleware({ included: [Tool] })); // Only show tool executions

  console.log(response.result.text);
  ```
</CodeGroup>

<Tip>
  `Middleware` can be attached per-run (affects only that run) or at agent construction (applies to all runs, no need to attach each time).
</Tip>

### Catching events

Sometimes you want to react to specific events. To see which events are emitted, you can use the `on` function.

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.backend import ChatModel
  from beeai_framework.tools.weather import OpenMeteoTool
  from beeai_framework.middleware.trajectory import GlobalTrajectoryMiddleware
  from beeai_framework.tools import Tool

  async def main():
      agent = RequirementAgent(
          llm=ChatModel.from_name("ollama:granite3.3"),
          tools=[OpenMeteoTool()]
      )

      response = await agent.run("What's the current weather in Paris?").on("*.*", lambda data, event: print(event.name, data))

      print(response.last_message.text)

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  import { RequirementAgent } from "beeai-framework/agents/requirement/agent";
  import { ChatModel } from "beeai-framework/backend/chat";
  import { OpenMeteoTool } from "beeai-framework/tools/weather/openMeteo";

  const agent = new RequirementAgent({
    llm: await ChatModel.fromName("ollama:granite3.3"),
    tools: [new OpenMeteoTool()],
  });

  const response = await agent
    .run({ prompt: "What's the current weather in Paris?" })
    .observe((emitter) => {
      emitter.match("*.*", (data, event) => console.log(event.name, data));
    });

  console.log(response.result.text);
  ```
</CodeGroup>

<Tip>
  Listening for all events can be noisy. Filter to the events you are interested in capturing.
</Tip>

Alternatively, you can listen for events on the class itself rather than for a specific run.

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.backend import ChatModel
  from beeai_framework.tools.weather import OpenMeteoTool
  from typing import Any
  from beeai_framework.emitter import EventMeta


  async def main():
      agent = RequirementAgent(
          llm=ChatModel.from_name("ollama:granite3.3"),
          tools=[OpenMeteoTool()]
      )

      @agent.emitter.on("*.*")
      async def handle_event(data: Any, event: EventMeta):
          print(event.name, data)

      response = await agent.run("What's the current weather in Paris?")
      print(response.last_message.text)

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  import { RequirementAgent } from "beeai-framework/agents/requirement/agent";
  import { ChatModel } from "beeai-framework/backend/chat";
  import { OpenMeteoTool } from "beeai-framework/tools/weather/openMeteo";

  const agent = new RequirementAgent({
    llm: await ChatModel.fromName("ollama:granite3.3"),
    tools: [new OpenMeteoTool()],
  });

  agent.emitter.match("*.*", (data, event) => {
    console.log(event.name, data);
  });

  const response = await agent.run({ prompt: "What's the current weather in Paris?" });
  console.log(response.result.text);
  ```
</CodeGroup>

<Tip>
  All events and appropriate typings are stored in the `events.py` file in the given module.

  All emitters inherit from the root emitter, which is accessible through `Emitter.root()`. You can use this to monitor all events happening in the application.
</Tip>

### Logging

<Info>
  **Relevant Module**: [Logger](../modules/logger)
</Info>

Logging is a way to provide visibility into the state of your application. Set the `Logger` level of granularity and place logging statements at key points throughout your agent process.

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.backend import ChatModel
  from beeai_framework.tools.search.wikipedia import WikipediaTool
  from beeai_framework.tools.weather import OpenMeteoTool
  from beeai_framework.middleware.trajectory import GlobalTrajectoryMiddleware
  from beeai_framework.tools import Tool
  from beeai_framework.logger import Logger

  async def main():
      # You create the logger and decide what to log
      logger = Logger("my-agent", level="TRACE")

      logger.info("Starting agent application")

      agent = RequirementAgent(
          llm=ChatModel.from_name("ollama:granite3.3"),
          role="friendly AI assistant",
          instructions="Be helpful and conversational in all your interactions. Use your tools to find accurate, current information.",
          tools=[WikipediaTool(), OpenMeteoTool()]
      )

      logger.debug("About to process user message")

      # The `included` parameter filters what types of operations to trace:
      # - [Tool]: Show only tool executions (function calls, API calls, etc.)
      # - [ChatModel]: Show only LLM calls (model inference, token usage)
      # - [Tool, ChatModel]: Show both tools and LLM interactions
      # - [] or None: Show everything (agents, tools, models, requirements)
      response = await agent.run(
          "What's the weather in Paris and tell me about the Eiffel Tower?"
      ).middleware(GlobalTrajectoryMiddleware(included=[Tool]))

      logger.info("Agent response generated")

      print(response.last_message.text)

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  COMING SOON
  ```
</CodeGroup>

**Logger Output**: Traditional log messages with timestamps

```
2024-01-15 10:30:45 | INFO | my-agent - Starting agent application
2024-01-15 10:30:45 | DEBUG | my-agent - About to process user message
2024-01-15 10:30:47 | INFO | my-agent - Agent response generated successfully
```

### OpenTelemetry / OpenInference

Logging to the console is great for development, but it's not enough for production monitoring. You can easily let
the framework send traces and metrics to external platforms like Arize Phoenix, LangFuse, LangSmith, and more.

<Note>
  To run this step: `pip install openinference-instrumentation-beeai opentelemetry-sdk opentelemetry-exporter-otlp`
</Note>

#### Set the Endpoint

Set the `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable. Some vendors also need API kesy like `OTEL_EXPORTER_OTLP_HEADERS="authorization=Bearer <token>`

<CodeGroup>
  ```bash Linux/macOS theme={null}
  export OTEL_EXPORTER_OTLP_ENDPOINT="https://your-otel-endpoint"
  ```

  ```bash Windows Powershell theme={null}
  $env:OTEL_EXPORTER_OTLP_ENDPOINT="https://your-otel-endpoint"
  ```
</CodeGroup>

<CodeGroup>
  ```py Python [expandable] theme={null}
  from openinference.instrumentation.beeai import BeeAIInstrumentor
  from opentelemetry import trace as trace_api
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
  from opentelemetry.sdk import trace as trace_sdk
  from opentelemetry.sdk.resources import Resource
  from opentelemetry.sdk.trace.export import SimpleSpanProcessor  # Use BatchSpanProcessor in prod

  def setup_observability() -> None:
      tracer_provider = trace_sdk.TracerProvider(resource=Resource.create({}))
      tracer_provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter()))
      trace_api.set_tracer_provider(tracer_provider)
      BeeAIInstrumentor().instrument()  # auto-instruments BeeAI Framework

  # Call this BEFORE creating/running any BeeAI agents
  setup_observability()
  ```

  ```ts TypeScript [expandable] theme={null}
  // Note: OpenTelemetry instrumentation for TypeScript is coming soon
  // For now, use standard OpenTelemetry setup with manual instrumentation
  ```
</CodeGroup>

Run your application and you should see traces and metrics in your selected dashboard.

## Enforce Rules with the `RequirementAgent`

Use **requirements** to control the agent's behavior. Let’s add the `ThinkTool` and set up a `ConditionalRequirement` to enforce rules on when and how tools should be used.

<Tip>
  Learn more about the [RequirementAgent](/modules/agents/requirement-agent)
</Tip>

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.agents.requirement.requirements.conditional import ConditionalRequirement
  from beeai_framework.backend import ChatModel
  from beeai_framework.tools.search.wikipedia import WikipediaTool
  from beeai_framework.tools.weather import OpenMeteoTool
  from beeai_framework.tools.think import ThinkTool
  from beeai_framework.middleware.trajectory import GlobalTrajectoryMiddleware
  from beeai_framework.tools import Tool
  from beeai_framework.logger import Logger

  async def main():
      logger = Logger("my-agent", level="TRACE")
      logger.info("Starting agent application")

      agent = RequirementAgent(
          llm=ChatModel.from_name("ollama:granite3.3"),
          role="friendly AI assistant",
          instructions="Be helpful and conversational in all your interactions. Use your tools to find accurate, current information.",
          tools=[WikipediaTool(), OpenMeteoTool(), ThinkTool()],
          requirements=[
              # Force agent to think before acting, and after each tool use
              ConditionalRequirement(
                  ThinkTool,
                  force_at_step=1,  # Always think first
                  force_after=Tool,  # Think after using any tool
                  consecutive_allowed=False  # Don't think twice in a row
              )
          ],
          middlewares=[GlobalTrajectoryMiddleware(included=[Tool])]
      )
      logger.debug("About to process user message")
      response = await agent.run(
          "What's the weather in Paris and tell me about the Eiffel Tower?"
      )
      logger.info("Agent response generated")
      logger.info(response.last_message.text)

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  import { RequirementAgent } from "beeai-framework/agents/requirement/agent";
  import { ConditionalRequirement } from "beeai-framework/agents/requirement/requirements/conditional";
  import { ChatModel } from "beeai-framework/backend/chat";
  import { WikipediaTool } from "beeai-framework/tools/search/wikipedia";
  import { OpenMeteoTool } from "beeai-framework/tools/weather/openMeteo";
  import { ThinkTool } from "beeai-framework/tools/think";
  import { GlobalTrajectoryMiddleware } from "beeai-framework/middleware/trajectory";
  import { Tool } from "beeai-framework/tools/base";
  import { Logger } from "beeai-framework/logger/logger";

  const logger = new Logger({ name: "my-agent", level: "trace" });
  logger.info("Starting agent application");

  const agent = new RequirementAgent({
    llm: await ChatModel.fromName("ollama:granite3.3"),
    role: "friendly AI assistant",
    instructions:
      "Be helpful and conversational in all your interactions. Use your tools to find accurate, current information.",
    tools: [new WikipediaTool(), new OpenMeteoTool(), new ThinkTool()],
    requirements: [
      // Force agent to think before acting, and after each tool use
      new ConditionalRequirement(ThinkTool, {
        forceAtStep: 1, // Always think first
        forceAfter: [Tool], // Think after using any tool
        consecutiveAllowed: false, // Don't think twice in a row
      }),
    ],
    middlewares: [new GlobalTrajectoryMiddleware({ included: [Tool] })],
  });

  logger.debug("About to process user message");
  const response = await agent.run({
    prompt: "What's the weather in Paris and tell me about the Eiffel Tower?",
  });
  logger.info("Agent response generated");
  logger.info(response.result.text);
  ```
</CodeGroup>

<Tip>
  Learn more about Requirements and see examples in [documentation](/modules/agents/requirement-agent)
</Tip>

### Request User Permission with the `AskPermissionRequirement`

Add user permission for when you want an action to be human validated before being executed:

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.agents.requirement.requirements.conditional import ConditionalRequirement
  from beeai_framework.agents.experimental.requirements.ask_permission import AskPermissionRequirement
  from beeai_framework.backend import ChatModel
  from beeai_framework.tools.search.wikipedia import WikipediaTool
  from beeai_framework.tools.weather import OpenMeteoTool
  from beeai_framework.tools.think import ThinkTool
  from beeai_framework.middleware.trajectory import GlobalTrajectoryMiddleware
  from beeai_framework.tools import Tool
  from beeai_framework.logger import Logger

  async def main():
      logger = Logger("my-agent", level="TRACE")
      logger.info("Starting agent application")

      agent = RequirementAgent(
          llm=ChatModel.from_name("ollama:granite3.3:8b"),
          role="friendly AI assistant",
          instructions="Be helpful and conversational in all your interactions. Use your tools to find accurate, current information.",
          tools=[WikipediaTool(), OpenMeteoTool(), ThinkTool()],
          requirements=[
              ConditionalRequirement(
                  ThinkTool,
                  force_at_step=1,
                  force_after=Tool,
                  consecutive_allowed=False
              ),
              AskPermissionRequirement([OpenMeteoTool])  # Ask before using weather API
          ]
      )
      logger.debug("About to process user message")
      response = await agent.run(
          "What's the weather in Paris?"
      ).middleware(GlobalTrajectoryMiddleware(included=[Tool]))
      logger.info("Agent response generated")
      print(response.last_message.text)

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  // Note: AskPermissionRequirement is not yet implemented in TypeScript
  // Coming soon
  ```
</CodeGroup>

In the console, the output looks like:

```
Do you allow it? (yes/no): yes
```

***

## Knowledge

Now it's time to integrate data. from a vector store using RAG (retrieval augmented generation)

<Info>
  Relevant Module: [RAG](../modules/rag)
</Info>

<Note>
  Install the RAG extras (if you haven’t already): `pip install "beeai-framework[rag]"` <br />

  Pull the `nomic-embed-text` model in Ollama. <br />

  Create synthetic or non-synthetic Markdown files to ingest into your vector store.
</Note>

Let's give your agent access to a knowledge base of documents.

### Setup the Vector Store, Pre-process, and Load the Documents

1. Create a new file and name it `step1_knowledge_base`
2. Copy the following code into the file and replace the `file_paths` with your own files

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  from beeai_framework.backend.document_loader import DocumentLoader
  from beeai_framework.backend.embedding import EmbeddingModel
  from beeai_framework.backend.text_splitter import TextSplitter
  from beeai_framework.backend.vector_store import VectorStore

  async def setup_knowledge_base():
      # Create embedding model using Ollama
      embedding_model = EmbeddingModel.from_name("ollama:nomic-embed-text")

      # Create vector store
      vector_store = VectorStore.from_name(
          "beeai:TemporalVectorStore",
          embedding_model=embedding_model
      )

      # Setup text splitter for chunking documents
      text_splitter = TextSplitter.from_name(
          "langchain:RecursiveCharacterTextSplitter",
          chunk_size=1000,
          chunk_overlap=200
      )

      return vector_store, text_splitter

  async def load_documents(vector_store, text_splitter, file_paths):
      """Load documents into the vector store"""
      all_chunks = []

      for file_path in file_paths:
          try:
              # Load the document
              loader = DocumentLoader.from_name(
                  "langchain:UnstructuredMarkdownLoader",
                  file_path=file_path
              )
              documents = await loader.load()

              # Split into chunks
              chunks = await text_splitter.split_documents(documents)
              all_chunks.extend(chunks)
              print(f"Loaded {len(chunks)} chunks from {file_path}")
          except Exception as e:
              print(f"Failed to load {file_path}: {e}")

      # Add all chunks to vector store
      if all_chunks:
          await vector_store.add_documents(all_chunks)
          print(f"Total chunks added: {len(all_chunks)}")

      return vector_store if all_chunks else None

  async def main():
      # Setup the knowledge base
      vector_store, text_splitter = await setup_knowledge_base()

      # Replace with your actual markdown files
      file_paths = [
          "your_document1.md", #replace these documents with the path to your local document
          "your_document2.md", #replace these documents with the path to your local document
      ]

      # Load documents
      loaded_vector_store = await load_documents(vector_store, text_splitter, file_paths)

      if loaded_vector_store:
          print("Knowledge base ready!")
          return loaded_vector_store
      else:
          print("No documents loaded")
          return None

  if __name__ == "__main__":
      # Run this first to setup your knowledge base
      asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  COMING SOON
  ```
</CodeGroup>

### Create RAG-Enabled Agent

3. Create a new file that imports the helper functions from the `step1_knowledge_base` file and uses the vector store setup in the previous step
4. Copy the following code into a new file and replace the `file_paths` with your own paths

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.backend import ChatModel
  from beeai_framework.tools.search.wikipedia import WikipediaTool
  from beeai_framework.tools.weather import OpenMeteoTool
  from beeai_framework.tools.search.retrieval import VectorStoreSearchTool

  # Import the setup function from Step 1
  from step1_knowledge_base import setup_knowledge_base, load_documents

  async def main():
      # Setup knowledge base (from Step 1)
      vector_store, text_splitter = await setup_knowledge_base()

      # Load your documents
      file_paths = [
          "your_document1.md", #replace these documents with the path to your local document
          "your_document2.md", #replace these documents with the path to your local document
      ]

      loaded_vector_store = await load_documents(vector_store, text_splitter, file_paths)

      if not loaded_vector_store:
          print("No documents loaded - exiting")
          return

      # Create RAG tool
      rag_tool = VectorStoreSearchTool(vector_store=loaded_vector_store)

      # Create agent with RAG capabilities
      agent = RequirementAgent(
          llm=ChatModel.from_name("ollama:granite3.3"),
          tools=[WikipediaTool(), OpenMeteoTool(), rag_tool],
          instructions="""You are a knowledgeable assistant with access to:
          1. A document knowledge base (use VectorStoreSearch for specific document queries)
          2. Wikipedia for general facts
          3. Weather information

          When users ask about topics that might be in the documents, search your knowledge base first."""
      )

      # Test the RAG-enabled agent
      response = await agent.run("What information do you have in your knowledge base?")
      print(response.last_message.text)

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  COMING SOON
  ```
</CodeGroup>

1. Add some markdown files with information about your company/project
2. Ask questions that should be answered from your documents
3. Compare how responses differ with vs. without the knowledge base or when using different pre-processing strategies

## Orchestration

<Info>
  Relevant Module: [Workflows](https://framework.beeai.dev/modules/workflows)
</Info>

### Multi-Agent Hand-offs

Create a team of specialized agents that can collaborate:

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.backend import ChatModel
  from beeai_framework.memory import UnconstrainedMemory
  from beeai_framework.tools.search.wikipedia import WikipediaTool
  from beeai_framework.tools.weather import OpenMeteoTool
  from beeai_framework.tools.handoff import HandoffTool
  from beeai_framework.tools.think import ThinkTool
  from beeai_framework.logger import Logger

  async def main():
      # Initialize logger
      logger = Logger("multi-agent-system", level="TRACE")
      logger.info("Starting multi-agent system")

      # Create specialized agents
      logger.debug("Creating knowledge agent")
      knowledge_agent = RequirementAgent(
          llm=ChatModel.from_name("ollama:granite3.3"),
          tools=[ThinkTool(), WikipediaTool()],
          memory=UnconstrainedMemory(),
          instructions="Provide detailed, accurate information using available knowledge sources. Think through problems step by step."
      )

      logger.debug("Creating weather agent")
      weather_agent = RequirementAgent(
          llm=ChatModel.from_name("ollama:granite3.3"),
          tools=[ThinkTool(), OpenMeteoTool()],
          memory=UnconstrainedMemory(),
          instructions="Provide comprehensive weather information and forecasts. Always think before using tools."
      )

      # Create a coordinator agent that manages handoffs
      logger.debug("Creating coordinator agent")
      coordinator_agent = RequirementAgent(
          llm=ChatModel.from_name("ollama:granite3.3"),
          memory=UnconstrainedMemory(),
          tools=[
              HandoffTool(
                  target=knowledge_agent,
                  name="knowledge_specialist",
                  description="For general knowledge and research questions"
              ),
              HandoffTool(
                  target=weather_agent,
                  name="weather_expert",
                  description="For weather-related queries"
              ),
          ],
          instructions="""You coordinate between specialist agents.
          - For weather queries: use weather_expert
          - For research/knowledge questions: use knowledge_specialist
          - For mixed queries: break them down and use multiple specialists

          Always introduce yourself and explain which specialist will help."""
      )

      logger.info("Running query: What's the weather in Paris and tell me about its history?")
      try:
          response = await coordinator_agent.run("What's the weather in Paris and tell me about its history?")
          logger.info("Query completed successfully")
          print(response.last_message.text)
      except Exception as e:
          logger.error(f"Error during agent execution: {e}")
          raise

      logger.info("Multi-agent system execution completed")

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  COMING SOON
  ```
</CodeGroup>

1. Ask the coordinator mixed questions: "What's the weather in Paris and tell me about its history?"
2. Test how it decides which agent to use
3. Try complex queries that need multiple specialists

### Advanced Workflows

<Tip>
  For complex, multi-step processes, a more advanced workflow system is coming soon! Join the discussion [here](https://github.com/i-am-bee/beeai-framework/discussions/1005)
</Tip>

***

## Production

Now it's time for production-grade features.

### Caching for Speed & Efficiency

<Info>
  Relevant Module: [Cache](../modules/cache)
</Info>

Caching helps you cut costs, reduce latency, and deliver consistent results by reusing previous computations. In BeeAI Framework, you can cache LLM responses and tool outputs.

<Note>
  Caching the entire agent isn’t practical—every agent run is usually unique. Instead, focus on caching the components inside your agent.
</Note>

**1. Caching LLM Calls**

Configure a cache on your LLM to avoid paying for repeated queries:

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  from beeai_framework.backend import ChatModel, UserMessage
  from beeai_framework.cache import SlidingCache

  async def main():
      # LLM with caching enabled
      llm = ChatModel.from_name("ollama:granite3.3")
      llm.config(cache=SlidingCache(size=50))  # Cache up to 50 responses

      # Must send a list of messages to the llm
      messages = [UserMessage("Hello, how are you?")]

      # First call (miss)
      res1 = await llm.run(messages)
      # Second call with identical input (hit)
      res2 = await llm.run(messages)

      print("First:", res1.last_message.text)
      print("Second (cached):", res2.last_message.text)

  asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  COMING SOON
  ```
</CodeGroup>

**2. Caching Tool Outputs**

Many tools query APIs or perform expensive lookups. You can attach a cache directly:

<CodeGroup>
  ```py Python [expandable] theme={null}
  from beeai_framework.tools.weather import OpenMeteoTool
  from beeai_framework.cache import UnconstrainedCache

  # Cache all results from the weather API
  weather_tool = OpenMeteoTool(options={"cache": UnconstrainedCache()})
  ```

  ```ts TypeScript [expandable] theme={null}
  COMING SOON
  ```
</CodeGroup>

**3. Using Cached Components in an Agent**

<CodeGroup>
  ```py Python [expandable] theme={null}
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.tools.weather import OpenMeteoTool
  from beeai_framework.cache import UnconstrainedCache
  from beeai_framework.memory import UnconstrainedMemory
  from beeai_framework.backend import ChatModel
  from beeai_framework.cache import SlidingCache
  import asyncio

  async def main():
      llm = ChatModel.from_name("ollama:granite3.3")
      llm.config(cache=SlidingCache(size=50))  # Cache up to 50 responses

      weather_tool = OpenMeteoTool(options={"cache": UnconstrainedCache()})

      agent = RequirementAgent(
          llm=llm,  # LLM with cache
          tools=[weather_tool],  # Tool with cache
          memory=UnconstrainedMemory(),
          instructions="Provide answers efficiently using cached results when possible."
      )

      response1 = await agent.run("What's the weather in New York?")
      response2 = await agent.run("What's the weather in New York?")  # Weather tool cache should hit

  asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  COMING SOON
  ```
</CodeGroup>

<Tip>
  Agent-level caching is rarely effective because every input is usually unique. Focus on caching LLMs and tools individually for best performance.
</Tip>

### Handle Errors Gracefully

<Info>
  Relevant Module: [Errors](https://framework.beeai.dev/modules/errors)
</Info>

Make your system robust with comprehensive error management:

<CodeGroup>
  ```py Python [expandable] theme={null}
  import asyncio
  import traceback
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.backend import ChatModel
  from beeai_framework.memory import UnconstrainedMemory
  from beeai_framework.tools.weather import OpenMeteoTool
  from beeai_framework.errors import FrameworkError

  async def main():
      try:
          agent = RequirementAgent(
              llm=ChatModel.from_name("ollama:granite3.3"),
              memory=UnconstrainedMemory(),
              tools=[OpenMeteoTool()],
              instructions="You provide weather information."
          )

          response = await agent.run("What's the weather in Invalid-City-Name?")
          print(response.last_message.text)

      except FrameworkError as e:
          print(f"Framework error occurred: {e.explain()}")
          traceback.print_exc()
      except Exception as e:
          print(f"Unexpected error: {e}")
          traceback.print_exc()

  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```ts TypeScript [expandable] theme={null}
  COMING SOON
  ```
</CodeGroup>

***

## Integration

<Info>
  Relevant Module: [Serve](../modules/serve), [MCP](../integrations/mcp), [A2A](../integrations/a2a), [IBM watsonX Orchestrate](../integrations/watsonx-orchestrate)
</Info>

### Model Context Protocol (MCP)

Expose your agent as an **MCP server**:

<CodeGroup>
  ```py Python [expandable] theme={null}
  from beeai_framework.adapters.mcp import MCPServer
  from beeai_framework.tools.weather import OpenMeteoTool
  from beeai_framework.tools.search.wikipedia import WikipediaTool

  def main():
      # Create an MCP server
      server = MCPServer()

      # Register tools that can be used by MCP clients
      server.register_many([
          OpenMeteoTool(),
          WikipediaTool()
      ])

      # Start the server
      server.serve()

  if __name__ == "__main__":
      main()
  ```

  ```ts TypeScript [expandable] theme={null}
  COMING SOON
  ```
</CodeGroup>

### Agent Stack

Expose your agent as a **Agent Stack server**:

<Tip>
  Make sure you have done the following:

  pip install `'beeai-framework[a2a]' 'beeai-framework[agentstack]'`  and you have a compatible version of `uvicorn` installed.
</Tip>

<CodeGroup>
  ```py Python [expandable] theme={null}
  from beeai_framework.adapters.agentstack.serve.server import AgentStackServer
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.backend import ChatModel
  from beeai_framework.memory import UnconstrainedMemory
  from beeai_framework.middleware.trajectory import GlobalTrajectoryMiddleware
  from beeai_framework.tools.search.wikipedia import WikipediaTool
  from beeai_framework.tools.weather import OpenMeteoTool

  def main():
      llm = ChatModel.from_name("ollama:granite3.3")
      agent = RequirementAgent(
          llm=llm,
          tools=[WikipediaTool(), OpenMeteoTool()],
          memory=UnconstrainedMemory(),
          middlewares=[GlobalTrajectoryMiddleware()],
          instructions="You are a helpful research assistant with access to Wikipedia and weather data."
      )

      # Runs HTTP server that registers to Agent Stack
      server = AgentStackServer(config={"configure_telemetry": False})
      server.register(agent)
      server.serve()

  if __name__ == "__main__":
      main()
  ```

  ```ts TypeScript [expandable] theme={null}
  COMING SOON
  ```
</CodeGroup>

### Agent2Agent (A2A) Protocol

Expose your agent as an **A2A server**:

<Tip>
  Make sure you have done the following:

  pip install `'beeai-framework[a2a]'`  and you have a compatible version of `uvicorn` installed.
</Tip>

<CodeGroup>
  ```py Python [expandable] theme={null}
  from beeai_framework.adapters.a2a import A2AServer, A2AServerConfig
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.backend import ChatModel
  from beeai_framework.memory import UnconstrainedMemory
  from beeai_framework.serve.utils import LRUMemoryManager
  from beeai_framework.tools.search.duckduckgo import DuckDuckGoSearchTool
  from beeai_framework.tools.weather import OpenMeteoTool


  def main() -> None:
      llm = ChatModel.from_name("ollama:granite3.3")
      agent = RequirementAgent(
          llm=llm,
          tools=[DuckDuckGoSearchTool(), OpenMeteoTool()],
          memory=UnconstrainedMemory(),
      )

      # Register the agent with the A2A server and run the HTTP server
      # we use LRU memory manager to keep limited amount of sessions in the memory
      A2AServer(config=A2AServerConfig(port=9999), memory_manager=LRUMemoryManager(maxsize=100)).register(agent).serve()


  if __name__ == "__main__":
      main()
  ```

  ```ts TypeScript [expandable] theme={null}
  COMING SOON
  ```
</CodeGroup>

### IBM watsonx Orchestrate

Expose your agent as an **IBM watsonx Orchestrate server**:

<CodeGroup>
  ```py Python [expandable] theme={null}
  from beeai_framework.adapters.watsonx_orchestrate import WatsonxOrchestrateServer, WatsonxOrchestrateServerConfig
  from beeai_framework.agents.requirement import RequirementAgent
  from beeai_framework.backend import ChatModel
  from beeai_framework.memory import UnconstrainedMemory
  from beeai_framework.serve.utils import LRUMemoryManager
  from beeai_framework.tools.weather import OpenMeteoTool

  def main() -> None:
      llm = ChatModel.from_name("ollama:granite3.3")
      agent = RequirementAgent(
          llm=llm,
          tools=[OpenMeteoTool()],
          memory=UnconstrainedMemory(),
          instructions="You are a weather agent that provides accurate weather information."
      )

      config = WatsonxOrchestrateServerConfig(port=8080, host="0.0.0.0", api_key=None)  # optional
      # use LRU memory manager to keep limited amount of sessions in the memory
      server = WatsonxOrchestrateServer(config=config, memory_manager=LRUMemoryManager(maxsize=100))
      server.register(agent)

      # start an API with /chat/completions endpoint which is compatible with Watsonx Orchestrate
      server.serve()


  if __name__ == "__main__":
      main()
  ```

  ```ts TypeScript [expandable] theme={null}
  COMING SOON
  ```
</CodeGroup>

***

## What's Next?

Congratulations! You've built a complete AI agent system from a simple chat bot to a production-ready, multi-agent workflow with knowledge bases, caching, error handling, and service endpoints.

Each module page includes detailed guides, examples, and best practices. Here are some next steps:

1. **Explore Modules:** Dive deeper into specific modules that interest you
2. **Scale Your System:** Add more agents, tools, and knowledge bases
3. **Custom Tools:** Build your own tools for domain-specific functionality

The framework is designed to scale with you. Start simple, then grow your system step by step as your needs evolve. You now have all the building blocks to create sophisticated and reliable AI agent systems!
