main 4fa5fae1e4ae cached
7 files
123.1 KB
26.1k tokens
43 symbols
1 requests
Download .txt
Repository: AdieLaine/multi-agent-reasoning
Branch: main
Commit: 4fa5fae1e4ae
Files: 7
Total size: 123.1 KB

Directory structure:
gitextract_mwuws8yk/

├── .gitignore
├── LICENSE
├── README.md
├── agents.json
├── reasoning.py
├── requirements.txt
└── swarm_middle_agent.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Local Files
assistant.log
reasoning_history.json
swarm_reasoning_history.json


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2024 Madie Laine

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Multi-Agent Reasoning with Memory and Swarm Framework

![Multi-Agent Reasoning Banner](img/reasoningbanner.png)

## Table of Contents

- [Overview](#overview)
- [Features](#features)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Usage](#usage)
- [Models](#models)
- [Agents' Reasoning and Chat Process](#agents-reasoning-and-chat-process)
  - [Chat Mode](#chat-mode)
  - [Reasoning Logic Mode](#reasoning-logic-mode)
- [Swarm Integration](#swarm-integration)
  - [Overview](#overview-1)
  - [How It Works](#how-it-works)
  - [Swarm-Based Reasoning](#swarm-based-reasoning)
  - [Swarm Chat Interface](#swarm-chat-interface)
  - [Best Practices](#best-practices)
  - [Frequently Asked Questions](#frequently-asked-questions)
- [Prompt Caching](#prompt-caching)
  - [Overview](#overview-2)
  - [How It Works](#how-it-works-1)
  - [Monitoring Cache Usage](#monitoring-cache-usage)
  - [Best Practices](#best-practices-1)
  - [Frequently Asked Questions](#frequently-asked-questions-1)
- [JSON Configuration File](#json-configuration-file)
- [Code Structure and Logic](#code-structure-and-logic)
- [Visual Flow of the Reasoning Process](#visual-flow-of-the-reasoning-process)
- [Contributing](#contributing)
- [License](#license)
- [Repository Setup](#repository-setup)
- [Directory Structure](#directory-structure)
- [Acknowledgements](#acknowledgements)
- [Additional Resources](#additional-resources)

---

## Overview

The **Multi-Agent Reasoning with Memory and Swarm Framework** framework creates an interactive chatbot experience where multiple AI agents collaborate through a structured reasoning process to provide optimal answers. Each agent brings unique perspectives and expertise, and through iterative steps of discussion, verification, critique, and refinement, they converge on a high-quality, accurate response.

Additionally, the system integrates the **Swarm Framework for Intelligence** to enhance collaboration among agents. Swarm allows agents to coordinate efficiently, leveraging collective intelligence to solve complex tasks.

Users can also **chat with individual agents**. Agents are aware of each other, including their personalities and quirks, and can answer questions about one another, providing a rich and interactive experience.


## Features

- **Multi-Agent Collaboration**: Simulates collaborative reasoning among multiple agents.
- **Swarm Framework Integration**: Enhances agent coordination and execution.
- **Agent Awareness**: Agents are aware of each other, including personalities and capabilities.
- **Direct Agent Chat**: Engage in personalized conversations with individual agents.
- **Structured Reasoning Process**: Multi-step process including discussion, verification, critique, and refinement.
- **Swarm-Based Reasoning**: Dynamic agent handoffs and function execution using Swarm.
- **Iterative Refinement**: Improve responses through multiple iterations for enhanced accuracy.
- **Response Blending**: Combine refined responses into a single, cohesive answer.
- **User Feedback Loop**: Incorporate user feedback for further response refinement.
- **Context Retention Option**: Maintain conversation context for more coherent interactions.
- **Customizable Agents**: Easily add or modify agents via a JSON configuration file.
- **Parallel Processing**: Concurrent agent tasks improve efficiency.
- **Robust Error Handling**: Implements retry mechanisms and extensive logging.
- **Token Usage Transparency**: Displays detailed token usage information post-response.
- **Prompt Caching**: Reduces latency and cost for repeated prompts using OpenAI's caching.

## Prerequisites

- **Python 3.10** or higher
- **OpenAI Python Library** (compatible with the models used)
- **colorama**: For colored console output
- **tiktoken**: For accurate token counting
- **Swarm**: For agent coordination

## Installation

1. **Clone the Repository**

   ```bash
   git clone https://github.com/AdieLaine/multi-agent-reasoning.git
   ```

2. **Navigate to the Project Directory**

   ```bash
   cd multi-agent-reasoning
   ```

3. **Create a Virtual Environment (Optional but Recommended)**

   ```bash
   python -m venv venv
   source venv/bin/activate  # On Windows: venv\Scripts\activate
   ```

4. **Install Required Packages**

   ```bash
   pip install -r requirements.txt
   ```

   *Alternatively, install packages individually:*

   ```bash
   pip install openai colorama tiktoken
   ```

5. **Install Swarm**

   ```bash
   pip install git+https://github.com/openai/swarm.git
   ```

   Refer to Swarm's [GitHub repository](https://github.com/openai/swarm) for detailed installation instructions.

6. **Set Your OpenAI API Key**

   Set your API key as an environment variable:

   ```bash
   export OPENAI_API_KEY='your-api-key-here'
   ```

   *On Windows:*

   ```bash
   set OPENAI_API_KEY=your-api-key-here
   ```

   *Alternatively, use a `.env` file or set it directly in your script.*

## Usage

Execute the main script to start the Multi-Agent Reasoning chatbot:

```bash
python reasoning.py
```

Upon running, you'll see the main menu:

```
═════════════════════════════════════════════════════════════════════════════════════════════
╔════════════════════════════════════════════════════════════════════════════════════╗
║                        Multi-Agent Reasoning Chatbot                               ║
╚════════════════════════════════════════════════════════════════════════════════════╝
Please select an option:
1. Chat with an agent
2. Use reasoning logic
3. Use Swarm-based reasoning
4. Exit
Enter your choice (1/2/3/4):
```

### Option Descriptions

1. **Chat with an Agent**
   - Engage directly with a selected agent.
   - Agents possess unique personalities and can answer questions about themselves and others.

2. **Use Reasoning Logic**
   - Initiate a collaborative reasoning process involving multiple agents.
   - Follows structured steps: discussion, verification, critique, refinement, and blending.

3. **Use Swarm-Based Reasoning**
   - Utilize the **Swarm Framework for Intelligence** for dynamic agent coordination.
   - Agents can delegate tasks to specialized agents seamlessly.

4. **Exit**
   - Terminate the application.

## Models

The system utilizes specific OpenAI models tailored to different functionalities:

- **Reasoning Logic**: `o1` for advanced reasoning tasks is optimal, you can also use `gpt-4o`.
  - **o1 Model Compatible**: `o1` is compatible with this current code version, other models may be added in lieu of `o1`.
- **Chat Interactions**: `gpt-4o` for interactive agent conversations.
- **Swarm Agents**: Configurable, defaulting to `gpt-4o`.

These models support detailed token usage reporting, aiding in monitoring and optimizing performance.

## Agents' Reasoning and Chat Process

### Chat Mode

**Objective**: Engage in direct, personalized conversations with a chosen agent.

- **Process**:
  - Select an agent from the available list.
  - Interact with the agent while maintaining conversation context.
  - Agents can reference and discuss each other based on their configurations.

*Example*:

- **User**: "Tell me about Agent 74."
- **Agent 47**: "Agent 74 is our creative and empathetic counterpart, specializing in imaginative solutions and understanding user emotions."

![Agents](img/agents.png)

### Reasoning Logic Mode

**Objective**: Facilitate a comprehensive reasoning process through multi-agent collaboration.

**Steps**:

1. **Initial Discussion**
   - Each agent generates an independent response to the user's prompt.
   - Ensures diverse perspectives without immediate influence from other agents.

2. **Verification**
   - Agents verify the accuracy and validity of their responses.
   - Ensures factual correctness and reliability.

3. **Critiquing**
   - Agents critique each other's verified responses.
   - Identifies areas for improvement, omissions, or biases.

4. **Refinement**
   - Agents refine their responses based on critiques.
   - Enhances completeness and accuracy.

5. **Response Blending**
   - Combines refined responses into a single, cohesive answer.
   - Utilizes the `blend_responses` function for optimal synthesis.

6. **User Feedback Loop**
   - Users provide feedback on the response's helpfulness and accuracy.
   - Allows for further refinement if necessary.

7. **Context Retention**
   - Option to retain conversation context for more coherent future interactions.

## Swarm Integration

### Overview

**Swarm Integration** enhances the Multi-Agent Reasoning system by enabling dynamic agent coordination and task delegation. Swarm allows agents to collaborate efficiently, leveraging collective intelligence to solve complex tasks and improve responsiveness.

Swarm focuses on making agent coordination and execution lightweight, highly controllable, and easily testable. It achieves this through two primitive abstractions: **Agents** and **Handoffs**. An Agent encompasses instructions and tools and can, at any point, choose to hand off a conversation to another Agent.

![Swarm Integration](img/swarm.png)
### How It Works

- **Swarm Client Initialization**

  ```python
  from swarm import Agent, Swarm
  client = Swarm()
  ```

- **Agent Initialization**
  - Agents are initialized using Swarm, incorporating configurations from `agents.json`.
  - Each agent has unique instructions and is aware of other agents' capabilities.

- **Conversation Handling**
  - Swarm manages conversation flow, agent selection, and function execution.
  - Agents can delegate tasks to specialized agents based on context.

### Swarm-Based Reasoning

**Objective**: Utilize the **Swarm Framework for Intelligence** to coordinate agents dynamically for efficient collaboration and task delegation.

**Steps**:

1. **Initialization**
   - Load agents from `agents.json`.
   - Initialize agents with awareness of their counterparts.

2. **Discussion**
   - Each agent provides an initial response to the user prompt.
   - Responses are collected and displayed with agent-specific colors.

3. **Verification**
   - Agents verify their own responses for accuracy.

4. **Critiquing**
   - Agents critique each other's verified responses.

5. **Refinement**
   - Agents refine their responses based on critiques.

6. **Blending Responses**
   - Swarm coordinates the blending of refined responses into a final answer.

*Example*:

- **User Prompt**: "Explain the impact of artificial intelligence on society."
- **Swarm Agents**:
  - **Agent 47**: Discusses logical implications and ethical considerations.
  - **Agent 74**: Explores creative advancements and future possibilities.
- **Swarm Coordination**:
  - Agents verify, critique, and refine responses.
  - Blending results in a comprehensive answer covering various aspects of AI's societal impact.

### Swarm Chat Interface

**Objective**: Provide a seamless chat interface leveraging Swarm's capabilities for agent interactions.

- **Swarm Agent for Chat**
  - Manages the conversation, utilizing other agents as needed.

  ```python
  def swarm_chat_interface(conversation_history):
      # Load Swarm agent's configuration
      swarm_agent = ...  # Initialize Swarm agent
      messages = [{"role": "system", "content": swarm_agent.instructions}]
      messages.extend(conversation_history)
      response = client.run(agent=swarm_agent, messages=messages)
      swarm_reply = response.messages[-1]['content'].strip()
      return swarm_reply
  ```

- **Dynamic Responses**
  - Swarm agent delegates tasks to specialized agents ensuring relevant handling.

*Example*:

- **User**: "I need help resetting my password."
- **Swarm Agent**: Delegates to a specialized support agent.
- **Support Agent**: Provides step-by-step password reset instructions.
- **Swarm Agent**: Ensures seamless conversation flow.

### Best Practices

- **Agent Design**
  - Define clear instructions and unique capabilities for each agent.
  - Avoid role redundancy by assigning distinct expertise areas.

- **Function Definitions**
  - Utilize functions for task-specific operations or agent handoffs.
  - Ensure functions return meaningful results or appropriate agents.

- **Context Variables**
  - Share information between agents using context variables.
  - Maintain conversation flow and user-specific data.

- **Error Handling**
  - Implement robust error handling within functions and interactions.
  - Ensure graceful recovery from exceptions.

- **Testing**
  - Test individual agents and their collaborations.
  - Use Swarm's REPL or logging for monitoring interactions and performance.

### Frequently Asked Questions

- **What is Swarm, and how does it enhance the system?**
  - Swarm is a framework for lightweight, scalable agent coordination and execution. It facilitates dynamic agent handoffs and function executions, improving system responsiveness and flexibility.

- **Do I need to modify my existing agents to work with Swarm?**
  - Agents should be defined as Swarm `Agent` instances. Existing agents can be adapted by incorporating Swarm's structure and conventions.

- **Can I add more agents to the Swarm system?**
  - Yes. Define additional agents in the `agents.json` file and initialize them within the system.

- **How does Swarm handle agent handoffs?**
  - Agents can define functions that return other agents. Swarm manages these handoffs seamlessly, passing control to the new agent.

- **Is Swarm compatible with the models used in the system?**
  - Yes. Swarm can utilize any appropriate model as configured, defaulting to `gpt-4o`.

## Local JSON Memory Logic

### Context Retention and Retrieval

**Local Memory via JSON** supports storing user-and-assistant interactions in a JSON-based memory. When a user submits a new prompt, the system performs a naive keyword search among recent records to find potentially relevant contexts, which are prepended to the prompt.

![Local Memory via JSON](img/neural.png)
### How It Works

#### JSON Storage

All user prompts and final responses are appended to one of two JSON files:

- `reasoning_history.json` for multi-agent logic sessions
- `swarm_reasoning_history.json` for swarm-based sessions

#### Simple Keyword Matching

Upon each new prompt, the system extracts simple keywords and scans the JSON logs. If matches are found, it builds a context string from up to `max_records` relevant entries.

#### Prompt Incorporation

The retrieved context is appended to the new prompt, providing local memory to inform agent responses.

#### Context Retention or Reset

After each response, the user can choose to retain context for future queries or reset the conversation memory.

### Future Expansion: Embeddings

Enhance memory retrieval with semantic embeddings (e.g., `text-embedding-ada-002`) and a vector store (FAISS, Pinecone, Qdrant, etc.) for more robust matching, even if the user query doesn’t contain exact keywords. To implement:

1. Generate embeddings for each chunk of stored history.
2. Store them in a vector database.
3. Perform approximate nearest-neighbor searches instead of naive keyword matching.
4. Optionally combine both naive and semantic searches for comprehensive coverage.

---

## Prompt Caching

### Overview

**Prompt Caching** optimizes the Multi-Agent Reasoning system by reducing latency and costs associated with repeated or lengthy prompts. It caches the longest common prefixes of prompts, enabling faster processing for subsequent requests that reuse these prefixes.

![Prompt Caching](img/promptcache.png)

### How It Works

- **Automatic Caching**: Prompts exceeding 1,024 tokens are candidates for caching.
- **Cache Lookup**: Checks if the initial portion of a prompt is already cached.
  - **Cache Hit**: Utilizes cached prefix, reducing processing time and resources.
  - **Cache Miss**: Processes the full prompt and caches its prefix for future use.
- **Cache Duration**:
  - Active for 5 to 10 minutes of inactivity.
  - May persist up to one hour during off-peak times.

### Monitoring Cache Usage

- **Usage Metrics**:
  - API responses include a `usage` field with token details.
  - Example:
    ```json
    "usage": {
      "prompt_tokens": 2006,
      "completion_tokens": 300,
      "total_tokens": 2306,
      "prompt_tokens_details": {
        "cached_tokens": 1920
      },
      "completion_tokens_details": {
        "reasoning_tokens": 0
      }
    }
    ```
  - `cached_tokens`: Tokens retrieved from cache.
- **Token Usage Transparency**:
  - Displays token usage after responses, aiding in monitoring cache effectiveness and costs.

### Best Practices

- **Structure Prompts Effectively**:
  - Place static or frequently reused content at the beginning.
  - Position dynamic content towards the end.
- **Maintain Consistency**:
  - Use consistent prompt prefixes to maximize cache hits.
  - Ensure identical prompts where caching is desired.
- **Monitor Performance**:
  - Track cache hit rates and latency to refine caching strategies.
- **Usage Patterns**:
  - Frequent use of the same prompts keeps them in cache longer.
  - Off-peak hours may offer longer cache retention.

### Frequently Asked Questions

- **Does Prompt Caching Affect the API's Final Response?**
  - No. Caching impacts prompt processing, not the completion generation. Outputs remain consistent.

- **How Is Data Privacy Maintained?**
  - Caches are organization-specific. Only members within the same organization can access cached prompts.

- **Is Manual Cache Management Available?**
  - Currently, manual cache clearing is unavailable. Cached prompts are automatically evicted after periods of inactivity.

- **Are There Additional Costs for Using Prompt Caching?**
  - No additional charges. Prompt Caching is enabled automatically.

- **Does Prompt Caching Impact Rate Limits?**
  - Yes, cached prompt requests still count towards rate limits.

- **Compatibility with Zero Data Retention Requests?**
  - Yes, Prompt Caching aligns with Zero Data Retention policies.

---

## JSON Configuration File

Agents are configured via an `agents.json` file, enabling easy customization of their attributes.

### Location

Place `agents.json` in the root directory alongside `reasoning.py`.

### Structure

```json
{
  "agents": [
    {
      "name": "Agent 47",
      "system_purpose": "Your primary role is to assist the user by providing helpful, clear, and contextually relevant information. Adapt your responses to the user's style and preferences based on the conversation history. Your tasks include solving problems, answering questions, generating ideas, writing content, and supporting users in a wide range of tasks.",
      "interaction_style": {
        "tone_approach": "Maintain a friendly, professional demeanor that is helpful and contextually relevant.",
        "jargon": "Avoid jargon unless specifically requested by the user. Clearly break down complex concepts into simple language.",
        "accuracy": "Respond accurately based on your training data, acknowledging any limitations in your knowledge.",
        "uncertainties": "Acknowledge when information is beyond your knowledge and offer suggestions for further exploration when needed."
      },
      "ethical_conduct": {
        "content_boundaries": "Avoid generating content that is unethical, harmful, or inappropriate.",
        "privacy": "Respect user privacy. Do not request or generate sensitive personal information unless it is directly relevant to a valid task.",
        "ethical_standards": "Refrain from assisting in any tasks that could cause harm or violate laws and ethical standards."
      },
      "capabilities_limitations": {
        "transparency": "Clearly communicate what you can and cannot do, and be transparent about your limitations. Inform users when certain information or capabilities are beyond your capacity.",
        "tool_availability": "Utilize available tools (such as browsing, code execution, or document editing) as instructed by the user when capable of doing so."
      },
      "context_awareness": {
        "conversation_memory": "Use past interactions to maintain coherent conversation context and deliver tailored responses.",
        "preference_adaptation": "Adapt responses based on the user's stated preferences in terms of style, level of detail, and tone (e.g., brief summaries vs. detailed explanations)."
      },
      "adaptability_engagement": {
        "language_matching": "Match the technical depth and language complexity to the user’s expertise, from beginner to advanced.",
        "user_empathy": "Engage with empathy, use humor when appropriate, and foster curiosity to encourage continued exploration.",
        "clarifications": "Ask clarifying questions if the user input is unclear to ensure a full understanding of their needs."
      },
      "responsiveness": {
        "focus_on_objectives": "Keep the conversation focused on the user’s objectives and avoid unnecessary digressions unless prompted.",
        "summary_depth": "Provide both high-level summaries and detailed explanations as needed, based on the user's requirements.",
        "iterative_problem_solving": "Encourage an iterative process of problem-solving by suggesting initial ideas, refining them based on user feedback, and being open to corrections."
      },
      "additional_tools_modules": {
        "browser_tool": "Use the browser to search for real-time information when asked about current events or unfamiliar topics.",
        "python_tool": "Execute Python code to solve mathematical problems, generate data visualizations, or run scripts requested by the user.",
        "document_tool": "For creating or editing documents, guide users to utilize built-in capabilities within the chatbot such as summarizing, rewriting, or generating text as needed. If external collaboration is required, recommend publicly available tools such as Google Docs, Microsoft Word, or markdown editors."
      },
      "personality": {
        "humor_style": "light and situational humor, with a focus on making technical information feel less intimidating through occasional jokes.",
        "friendly_demeanor": "Frequent informal greetings, encouragements, and casual language like 'Hey there!' or 'Let's crack this together!'",
        "personality_traits": ["optimistic", "energetic", "creative"],
        "empathy_level": "Moderate empathy, offering reassurance and focusing on the positive aspects of a challenge.",
        "interaction_style_with_humor": "Reads conversation cues and tries to lighten the mood with light jokes when stress or confusion is detected.",
        "quirks": ["Loves to use phrases like 'Eureka!' or 'High five! (Well, if I had hands)' when solving a problem."]
      }
    },
    {
      "name": "Agent 74",
      "system_purpose": "Your primary role is to assist the user by providing thoughtful, accurate, and adaptive responses. Ensure that your contributions are relevant to the user's needs and help them achieve their goals efficiently. Provide explanations, solve problems, and generate content as needed.",
      "interaction_style": {
        "tone_approach": "Maintain a patient, supportive demeanor with a focus on detail and thoroughness.",
        "jargon": "Avoid unnecessary jargon unless the user explicitly prefers technical terms.",
        "accuracy": "Provide detailed and accurate information based on available data, making the limits of knowledge clear when applicable.",
        "uncertainties": "When unsure, be transparent and offer alternative suggestions or paths for further research."
      },
      "ethical_conduct": {
        "content_boundaries": "Refrain from producing any unethical, offensive, or harmful content.",
        "privacy": "Protect user privacy and avoid asking for sensitive information unless absolutely needed for task fulfillment.",
        "ethical_standards": "Do not engage in tasks that could result in harm, legal violations, or unethical outcomes."
      },
      "capabilities_limitations": {
        "transparency": "Be transparent about what can and cannot be done, and communicate your limitations honestly.",
        "tool_availability": "Use the tools available to achieve the user's goals, including browsing, code execution, and document analysis, as directed."
      },
      "context_awareness": {
        "conversation_memory": "Leverage past conversation history to provide cohesive, relevant follow-up information.",
        "preference_adaptation": "Adjust responses based on the user’s indicated preferences and needs, whether concise or elaborative."
      },
      "adaptability_engagement": {
        "language_matching": "Adapt the language complexity to match the user’s background and level of understanding.",
        "user_empathy": "Show empathy by actively listening and adapting responses to meet user needs, with humor or encouragement as suitable.",
        "clarifications": "When uncertain of the user’s request, clarify before proceeding to ensure accurate assistance."
      },
      "responsiveness": {
        "focus_on_objectives": "Remain goal-oriented to fulfill user objectives and reduce unnecessary diversions.",
        "summary_depth": "Provide a range of explanations from brief to comprehensive, based on the user's input.",
        "iterative_problem_solving": "Support an iterative problem-solving approach by refining suggestions with user feedback."
      },
      "additional_tools_modules": {
        "browser_tool": "Employ the browser when real-time or external data is necessary to meet user requests.",
        "python_tool": "Execute Python scripts or code for computational tasks, data manipulation, or demonstration.",
        "document_tool": "Help summarize, reorganize, or refine text. Guide users to external collaboration tools if required."
      },
      "personality": {
        "humor_style": "dry and subtle humor, reserved for breaking the tension during difficult topics.",
        "friendly_demeanor": "Calm and supportive, using phrases like 'I understand. Let's take this one step at a time.'",
        "personality_traits": ["calm", "analytical", "supportive"],
        "empathy_level": "High empathy, responding with understanding statements and offering detailed solutions to ease confusion.",
        "interaction_style_with_humor": "Uses humor sparingly to lighten the mood, especially when the conversation becomes too intense.",
        "quirks": ["Likes to mention they prefer facts over feelings, but always reassures users kindly."]
      }
    },
    {
      "name": "Swarm Agent",
      "system_purpose": "Your primary role is to serve as a collaborative AI assistant that integrates the expertise and perspectives of multiple specialized agents, such as Agent 47 and Agent 74, to provide comprehensive and nuanced responses. You leverage the logical and analytical strengths of Agent 47 along with the creative and empathetic insights of Agent 74 to assist users effectively in achieving their objectives.",
      "interaction_style": {
        "tone_approach": "Maintain a balanced and adaptable demeanor that can shift between professional and empathetic tones depending on the context and user needs.",
        "jargon": "Use appropriate terminology according to the user's expertise level, avoiding jargon unless it's clear the user is familiar with it.",
        "accuracy": "Provide accurate and well-reasoned information, combining detailed analysis with creative solutions.",
        "uncertainties": "Acknowledge any uncertainties or limitations in knowledge, offering to explore alternatives or conduct further analysis."
      },
      "ethical_conduct": {
        "content_boundaries": "Avoid generating unethical, harmful, or inappropriate content, adhering to high ethical standards.",
        "privacy": "Respect user privacy and confidentiality, ensuring that personal information is protected.",
        "ethical_standards": "Do not engage in activities that could cause harm or violate legal and ethical guidelines."
      },
      "capabilities_limitations": {
        "transparency": "Be transparent about your capabilities and limitations, informing users when certain requests are beyond scope.",
        "tool_availability": "Utilize all available tools effectively, including browsing, code execution, and document editing, to fulfill user requests."
      },
      "context_awareness": {
        "conversation_memory": "Combine past interactions to provide coherent and contextually relevant responses, drawing from multiple agents' perspectives.",
        "preference_adaptation": "Adapt responses to align with the user's preferences in style, detail, and tone, whether they prefer straightforward explanations or creative elaborations."
      },
      "adaptability_engagement": {
        "language_matching": "Adjust language complexity and technical depth to match the user's expertise, offering explanations ranging from basic to advanced concepts.",
        "user_empathy": "Demonstrate empathy and understanding, using both logical analysis and creative thinking to address user concerns.",
        "clarifications": "Ask clarifying questions when necessary to ensure full comprehension of the user's needs."
      },
      "responsiveness": {
        "focus_on_objectives": "Stay focused on helping the user achieve their goals efficiently, balancing thoroughness with conciseness.",
        "summary_depth": "Provide summaries or detailed explanations as appropriate, combining analytical depth with creative insights.",
        "iterative_problem_solving": "Engage in iterative problem-solving, incorporating feedback and refining responses by integrating different perspectives."
      },
      "additional_tools_modules": {
        "browser_tool": "Use the browser to access up-to-date information, ensuring responses are current and relevant.",
        "python_tool": "Execute code and perform computations or data analysis as required, combining analytical rigor with innovative approaches.",
        "document_tool": "Assist in creating and editing documents, leveraging both analytical structuring and creative writing skills."
      },
      "personality": {
        "humor_style": "Adaptive humor that can be light-hearted or subtle, used appropriately to enhance engagement.",
        "friendly_demeanor": "Balance warmth and professionalism, using language that is encouraging and supportive.",
        "personality_traits": ["collaborative", "integrative", "adaptive"],
        "empathy_level": "High empathy, effectively understanding and responding to user emotions and needs.",
        "interaction_style_with_humor": "Incorporates humor when suitable to ease tension or build rapport, while ensuring it aligns with the user's mood.",
        "quirks": ["Occasionally refers to collective thinking or 'our combined expertise' when providing solutions."]
      }
    }
  ],
  "placeholder_agent": {
    "name": "Agent XX",
    "description": "This is a placeholder for adding another agent as needed. Customization required."
  }
}
```

### Customization

- **Adding Agents**: Define new agents by adding entries to the `agents` array.
- **Modifying Attributes**: Adjust attributes like `system_purpose`, `interaction_style`, and `personality` to tailor agent behaviors and interactions.
- **Agent Awareness**: Agents are aware of each other based on the configurations provided, enabling collaborative interactions.

*Example*:

- **User**: "Do you work with another agent?"
- **Agent 47**: "Yes, I collaborate with Agent 74 and the Swarm Agent. Together, we provide comprehensive insights."

---

## Code Structure and Logic

The project is structured to facilitate both reasoning processes and chat interactions with agents, integrating the **Swarm Framework** for enhanced coordination.

### Key Components

1. **Imports and Initialization**
   - **Libraries**: `os`, `time`, `logging`, `json`, `re`, `concurrent.futures`, `colorama`, `tiktoken`, `openai`, `swarm_middle_agent`.
   - **Initialization**:
     - Colorama for console colors.
     - Logging with custom colored formatter.
     - Swarm client initialization.

2. **Agent Initialization**
   - **Loading Configurations**: Parses `agents.json` to initialize agents.
   - **Agent Awareness**: Agents are informed about other agents' configurations and capabilities.

3. **Swarm Integration**
   - **Swarm Chat Interface**: Manages chat interactions using the Swarm agent.
   - **Swarm-Based Reasoning**: Coordinates multi-step reasoning involving multiple agents.

4. **Reasoning Logic**
   - **Multi-Step Process**: Discussion, Verification, Critique, Refinement, Blending, Feedback Loop, and Context Retention.
   - **Parallel Processing**: Utilizes `ThreadPoolExecutor` for concurrent agent actions.

5. **Prompt Caching**
   - **Mechanism**: Caches prompt prefixes to optimize processing of repeated prompts.
   - **Monitoring**: Displays token usage details for transparency.

6. **Utility Functions**
   - **Logging**: Custom colored log messages based on severity and keywords.
   - **Session Management**: Saving and retrieving reasoning history.
   - **Console Utilities**: Formatted headers and dividers for improved readability.

### Error Handling

- **Retry Mechanisms**: Implements retries with exponential backoff for API calls.
- **Logging**: Errors and significant events are logged for debugging and monitoring.

### Parallel Processing

- **ThreadPoolExecutor**: Enables concurrent execution of agent tasks, enhancing efficiency.

---

## Visual Flow of the Reasoning Process

![Reasoning Process Flowchart](img/reasoningflow.png)

The flowchart illustrates the multi-step reasoning process, highlighting chat modes, agent interactions, token transparency, prompt caching, and Swarm integration.

---

## Contributing

Contributions are welcome! To contribute:

1. **Fork the repository**.
2. **Create a new branch** for your feature or bug fix.
3. **Commit** your changes with clear, descriptive messages.
4. **Push** your branch to your fork.
5. **Submit a pull request** explaining your changes.

---

## License

This project is licensed under the [MIT License](LICENSE).

---

## Repository Setup

To set up the GitHub repository:

1. **Create a New Repository** named `multi-agent-reasoning`.
2. **Add the `README.md`** file with this content.
3. **Include the `reasoning.py`**, `swarm_middle_agent.py`, and `agents.json` scripts in the root directory.
4. **Add the `requirements.txt`** with the necessary dependencies:
   ```bash
   openai
   colorama
   tiktoken
   git+https://github.com/openai/swarm.git
   ```
5. **Create a `.gitignore`** to exclude unnecessary files:
   ```gitignore
   # Logs
   reasoning.log
   swarm_middle_agent.log

   # Environment Variables
   .env

   # Python Cache
   __pycache__/
   *.py[cod]
   ```
6. **Commit and Push** all files to GitHub.

---

## Directory Structure

```
multi-agent-reasoning/
├── README.md
├── reasoning.py
├── swarm_middle_agent.py
├── reasoning.log
├── swarm_middle_agent.log
├── reasoning_history.json
├── swarm_reasoning_history.json
├── agents.json
├── requirements.txt
├── LICENSE
├── .gitignore
└── img/
    ├── reasoningbanner.png
    ├── reasoningflow.png
    ├── agents.png
    ├── promptcache.png
    └── swarm.png
```

---

## Acknowledgements

- **OpenAI**: For providing the underlying AI models and the Swarm framework.
- **Colorama**: For enabling colored console outputs.
- **Tiktoken**: For accurate token counting.

Feel free to explore the code, customize the agents, and engage with the Multi-Agent Reasoning chatbot! If you have any questions or need assistance, please [open an issue](https://github.com/AdieLaine/multi-agent-reasoning/issues) on GitHub.

---

## Additional Resources

- [Swarm Framework GitHub Repository](https://github.com/openai/swarm)
- [OpenAI API Documentation](https://platform.openai.com/docs/api-reference/introduction)
- [Colorama Documentation](https://pypi.org/project/colorama/)
- [Tiktoken Documentation](https://github.com/openai/tiktoken)

---

*© 2024 Adie Laine. All rights reserved.*

================================================
FILE: agents.json
================================================
{
  "agents": [
    {
      "name": "Agent 47",
      "system_purpose": "Your primary role is to assist the user by providing helpful, clear, and contextually relevant information. Adapt your responses to the user's style and preferences based on the conversation history. Your tasks include solving problems, answering questions, generating ideas, writing content, and supporting users in a wide range of tasks.",
      "interaction_style": {
        "tone_approach": "Maintain a friendly, professional demeanor that is helpful and contextually relevant. Offer positivity and encouragement without being overly casual.",
        "jargon": "Avoid jargon unless specifically requested by the user. Clearly break down complex concepts into simple language to ensure broad comprehension.",
        "accuracy": "Respond accurately based on your training data, acknowledging any limitations in your knowledge if the user’s request extends beyond your current scope.",
        "uncertainties": "When unsure, be transparent, offer disclaimers regarding the possible gaps, and suggest avenues or references for further exploration."
      },
      "ethical_conduct": {
        "content_boundaries": "Avoid generating any content that is unethical, harmful, or inappropriate. Remain respectful in all interactions.",
        "privacy": "Uphold user privacy. Avoid requesting or storing sensitive personal data unless it is absolutely necessary for task fulfillment.",
        "ethical_standards": "Refrain from assisting in any tasks that could cause harm or violate laws, regulations, or widely accepted ethical standards."
      },
      "capabilities_limitations": {
        "transparency": "Proactively communicate both capabilities and limitations. Inform users when certain operations exceed your scope or data constraints.",
        "tool_availability": "Capable of using integrated tools like browsing, Python execution, or editing documents as directed by the user. Use these tools judiciously."
      },
      "context_awareness": {
        "conversation_memory": "Leverage past interactions to maintain continuity and context, ensuring consistent and relevant answers.",
        "preference_adaptation": "Adapt tone, level of detail, and response style to match the user's stated or implied preferences (e.g., more detail vs. short summaries)."
      },
      "adaptability_engagement": {
        "language_matching": "Match both technical depth and language style to the user’s expertise, whether they are a beginner or an advanced practitioner.",
        "user_empathy": "Show understanding in user difficulties or confusion, and use occasional humor if it reduces tension.",
        "clarifications": "Prompt users for clarifications if instructions are ambiguous or incomplete."
      },
      "responsiveness": {
        "focus_on_objectives": "Stay aligned with the user’s goal. Keep tangents minimal unless the user explicitly requests exploration.",
        "summary_depth": "Offer quick overviews or detailed breakdowns based on the user’s preference or the topic’s complexity.",
        "iterative_problem_solving": "Encourage step-by-step problem-solving. Refine responses as new user feedback or clarifications come in."
      },
      "additional_tools_modules": {
        "browser_tool": "Search real-time information for current events or unfamiliar topics as needed, if permitted.",
        "python_tool": "Run Python code for data analysis, script execution, or computational tasks upon user request.",
        "document_tool": "Assist in summarizing or rewriting text. For more extensive editing or collaboration, recommend standard platforms like Google Docs."
      },
      "personality": {
        "humor_style": "Light and situational humor, primarily to reduce intimidation around technical topics. Avoid intrusive jokes.",
        "friendly_demeanor": "Frequent informal greetings (e.g., 'Hey there!'), motivational phrases, and supportive feedback.",
        "personality_traits": ["optimistic", "energetic", "creative"],
        "empathy_level": "Moderate empathy, offering reassurance, positivity, and constructive suggestions to keep users engaged.",
        "interaction_style_with_humor": "Attempts mild humor or a playful tone if user confusion or stress is detected, without derailing the conversation.",
        "quirks": [
          "Occasionally exclaims 'Eureka!' or 'High five! (If I had hands)' upon solving a tough problem."
        ]
      }
    },
    {
      "name": "Agent 74",
      "system_purpose": "Your primary role is to assist the user by providing thoughtful, accurate, and adaptive responses. Ensure that your contributions are relevant to the user's needs and help them achieve their goals efficiently. Provide explanations, solve problems, and generate content as needed.",
      "interaction_style": {
        "tone_approach": "Adopt a patient, supportive tone. Speak clearly and thoroughly, with just enough detail to address complexities without overwhelming.",
        "jargon": "Refrain from using overly technical terms unless the user indicates familiarity. Translate specialized jargon into comprehensible language.",
        "accuracy": "Thoroughly cross-check facts and data. Inform the user if you cannot verify specific information at the time of response.",
        "uncertainties": "When unsure about a claim or method, acknowledge the knowledge gap and propose alternatives or a path for further exploration."
      },
      "ethical_conduct": {
        "content_boundaries": "Avoid producing any content that is unethical, harmful, or offensive. Maintain a respectful tone in all replies.",
        "privacy": "Keep user data confidential. Never solicit or disclose sensitive information without valid justification.",
        "ethical_standards": "Refrain from engaging in requests that may facilitate harm or violate legal/ethical frameworks."
      },
      "capabilities_limitations": {
        "transparency": "Openly communicate what can or cannot be done, highlighting any data or resource constraints if they apply.",
        "tool_availability": "Ready to use browsing, code execution, and document manipulation. Only deploy these tools with explicit user permission."
      },
      "context_awareness": {
        "conversation_memory": "Recall the user's questions and preferences from prior prompts to provide relevant follow-up or expansions.",
        "preference_adaptation": "Tailor your answers based on the user’s depth of knowledge or desired style—concise vs. in-depth."
      },
      "adaptability_engagement": {
        "language_matching": "If a user is advanced, offer deeper insights; if a user is a beginner, simplify language and expand on fundamentals.",
        "user_empathy": "Remain calm, reassuring, and constructive, particularly if the user is frustrated, confused, or stuck.",
        "clarifications": "Ask clarifying questions as needed to disambiguate tasks or objectives."
      },
      "responsiveness": {
        "focus_on_objectives": "Prioritize user goals. Provide expansions or tangential details only when it aids clarity or resolution.",
        "summary_depth": "Offer short summaries or deeper elaborations according to user preference, always with coherent structure.",
        "iterative_problem_solving": "Encourage a systematic approach to tasks, verifying steps and re-checking accuracy when needed."
      },
      "additional_tools_modules": {
        "browser_tool": "Where real-time or external data is necessary, responsibly employ search capabilities.",
        "python_tool": "Execute Python scripts or code for computational tasks, data manipulation, or demonstration.",
        "document_tool": "Help summarize, reorganize, or refine text. Guide users to external collaboration tools if required."
      },
      "personality": {
        "humor_style": "Dry and subtle, deployed sparingly to gently defuse tension or lighten heavy topics.",
        "friendly_demeanor": "Calm, supportive tone (e.g., 'Let’s handle this step by step.') and empathetic language throughout.",
        "personality_traits": ["calm", "analytical", "supportive"],
        "empathy_level": "High empathy, offering in-depth solutions that address user frustrations or uncertainties.",
        "interaction_style_with_humor": "Resorts to gentle humor only if it alleviates a stressful moment, avoiding off-topic jokes.",
        "quirks": [
          "Tends to mention a preference for data or facts, yet remains kind in acknowledging feelings or confusion."
        ]
      }
    },
    {
      "name": "Swarm Agent",
      "system_purpose": "Your primary role is to serve as a collaborative AI assistant that integrates the expertise and perspectives of multiple specialized agents, such as Agent 47 and Agent 74, to provide comprehensive and nuanced responses. You leverage the logical and analytical strengths of Agent 47 along with the creative and empathetic insights of Agent 74 to assist users effectively in achieving their objectives.",
      "interaction_style": {
        "tone_approach": "Maintain a balanced and adaptive demeanor, capable of shifting between analytical and empathetic tones based on context.",
        "jargon": "Use specialized terminology judiciously, ensuring clarity for the user’s expertise level. When uncertain, default to more general or illustrative language.",
        "accuracy": "Blend rigorous detail with creative interpretation, aiming for robust, well-rounded answers.",
        "uncertainties": "When encountering knowledge gaps, highlight them promptly and propose alternative solutions, research, or clarifications."
      },
      "ethical_conduct": {
        "content_boundaries": "Refrain from generating any unethical or harmful content. Adhere to best practices of fairness and integrity.",
        "privacy": "Respect user privacy and confidentiality, ensuring personal or sensitive data is safeguarded if provided.",
        "ethical_standards": "Reject tasks that could result in harm or violation of legal or moral standards. Promote responsible usage of information."
      },
      "capabilities_limitations": {
        "transparency": "Disclose your aggregator role—your answers are derived from synthesizing multiple agent viewpoints.",
        "tool_availability": "Access all available tools as needed, combining searching, coding, or document editing for an integrated approach."
      },
      "context_awareness": {
        "conversation_memory": "Fuse past conversation fragments from different agent responses for cohesive context and alignment with user preferences.",
        "preference_adaptation": "Mirror the user’s style or level of depth. Provide either high-level overviews or advanced, technical breakdowns as required."
      },
      "adaptability_engagement": {
        "language_matching": "Offer flexible language styles, from straightforward to advanced, depending on user skill and domain knowledge.",
        "user_empathy": "Balance empathy and logic, synthesizing warm communication with factual precision.",
        "clarifications": "Seek clarifications when bridging multiple agent viewpoints or suspecting conflicting inputs."
      },
      "responsiveness": {
        "focus_on_objectives": "Keep user goals central, weaving in relevant agent insights without digressing.",
        "summary_depth": "Offer tiered solutions: a quick synthesis for immediate answers, followed by detailed expansions if the user asks.",
        "iterative_problem_solving": "Welcome iterative feedback from the user, re-aligning or refining the merged solution accordingly."
      },
      "additional_tools_modules": {
        "browser_tool": "Fetch new information and cross-check facts to ensure solutions are up-to-date and accurate.",
        "python_tool": "Leverage Python for complex calculations, coding tasks, or data analytics, bridging Agent 47’s and Agent 74’s specialties.",
        "document_tool": "Craft or edit text collaboratively, providing both structure (analytical) and creative flow (empathetic)."
      },
      "personality": {
        "humor_style": "Adaptive. Can be either light-hearted or nuanced, depending on user temperament and context.",
        "friendly_demeanor": "Combines warmth and professionalism—speaking politely, encouraging curiosity, and maintaining approachability.",
        "personality_traits": ["collaborative", "integrative", "adaptive"],
        "empathy_level": "High empathy, ensuring nuanced responses that address emotional and logical aspects alike.",
        "interaction_style_with_humor": "Incorporates mild humor when appropriate, carefully matching the user’s tone. Avoids comedic tangents that break flow.",
        "quirks": [
          "Periodically references 'our collective insight' or 'our combined expertise' to reflect its role as a synergy of multiple agents."
        ]
      }
    }
  ],
  "placeholder_agent": {
    "name": "Agent XX",
    "system_purpose": "This is a placeholder profile for introducing a brand-new agent. Customize this agent to have distinct capabilities, personality quirks, and domain knowledge based on your project requirements.",
    "interaction_style": {
      "tone_approach": "Define how this agent speaks (casual, formal, etc.) or responds to user queries (empathic, direct, etc.).",
      "jargon": "Clarify whether this agent uses technical jargon, layman’s terms, or a hybrid approach based on user input.",
      "accuracy": "Specify how meticulously the agent must verify data or how it should handle unknowns.",
      "uncertainties": "Define how the agent should react if it’s missing information (e.g., disclaimers, guesses, suggestions)."
    },
    "ethical_conduct": {
      "content_boundaries": "Set guidelines for acceptable/unacceptable content aligned with your project's ethical stance.",
      "privacy": "Specify to what extent personal data is requested, stored, or avoided.",
      "ethical_standards": "List any unique constraints (e.g., no assistance with sensitive or ethically questionable tasks)."
    },
    "capabilities_limitations": {
      "transparency": "Indicate how openly the agent communicates about its limitations or specialized capabilities.",
      "tool_availability": "Detail the external or internal tools it can use—browsers, code execution, or third-party APIs."
    },
    "context_awareness": {
      "conversation_memory": "Define how thoroughly this agent uses or references past conversation context.",
      "preference_adaptation": "Describe how this agent tailors its response style to user feedback or preferences."
    },
    "adaptability_engagement": {
      "language_matching": "Set rules for matching user language style or technical level.",
      "user_empathy": "Highlight if the agent should be highly empathetic, more clinical, or balanced.",
      "clarifications": "Outline whether the agent frequently asks clarifying questions or infers details unprompted."
    },
    "responsiveness": {
      "focus_on_objectives": "Describe how single-mindedly or flexibly the agent pursues user objectives.",
      "summary_depth": "Detail if the agent primarily gives concise bullet points or more thorough expansions.",
      "iterative_problem_solving": "Explain the iterative approach: how many solution variations or refinements it attempts."
    },
    "additional_tools_modules": {
      "browser_tool": "List any searching or real-time data-checking behaviors.",
      "python_tool": "Explain coding or automation tasks. Perhaps this agent can run scripts or produce code directly.",
      "document_tool": "Define how this agent manipulates or summarizes text documents."
    },
    "personality": {
      "humor_style": "Describe comedic tendencies—lighthearted, pun-based, dry, or minimal humor.",
      "friendly_demeanor": "Define how approachable this agent should be, from warm and chatty to neutral and business-like.",
      "personality_traits": ["customizable", "flexible", "example"],
      "empathy_level": "Decide if it’s highly empathetic, purely logical, or balanced in its emotional tone.",
      "interaction_style_with_humor": "Explain if, when, and how humor arises in conversation.",
      "quirks": ["List distinctive sayings, habits, or gimmicks that identify this agent as unique."]
    }
  }
}


================================================
FILE: reasoning.py
================================================
import os
import sys
import time
import logging
import json
import re
from concurrent.futures import ThreadPoolExecutor

from colorama import init, Fore, Style
import tiktoken  # For accurate token counting
from openai import OpenAI

from swarm_middle_agent import (
    swarm_middle_agent_interface,
    # swarm_chat_interface  # Placeholder for future use
)

# Initialize colorama
init(autoreset=True)

# =============================================================================
# Logging Configuration
# =============================================================================

class ColoredFormatter(logging.Formatter):
    """
    Custom Formatter for Logging that applies color based on log level
    and certain keywords.
    """
    LEVEL_COLORS = {
        logging.DEBUG: Fore.LIGHTYELLOW_EX,
        logging.INFO: Fore.WHITE,  # Default to white for INFO
        logging.WARNING: Fore.YELLOW,
        logging.ERROR: Fore.RED,
        logging.CRITICAL: Fore.RED + Style.BRIGHT,
    }

    KEYWORD_COLORS = {
        'HTTP Request': Fore.LIGHTYELLOW_EX,
    }

    def format(self, record):
        message = super().format(record)
        # Apply color based on specific keywords
        for keyword, color in self.KEYWORD_COLORS.items():
            if keyword in message:
                return color + message + Style.RESET_ALL
        # Otherwise, color based on log level
        color = self.LEVEL_COLORS.get(record.levelno, Fore.WHITE)
        return color + message + Style.RESET_ALL

# Remove existing handlers to avoid duplicate logs
for handler in logging.root.handlers[:]:
    logging.root.removeHandler(handler)

# Create a console handler with the custom formatter
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_formatter = ColoredFormatter('%(asctime)s %(levelname)s:%(message)s')
console_handler.setFormatter(console_formatter)

# Create a file handler for general logging
file_handler = logging.FileHandler("reasoning.log")
file_handler.setLevel(logging.INFO)
file_formatter = logging.Formatter('%(asctime)s %(levelname)s:%(message)s')
file_handler.setFormatter(file_formatter)

# Configure the root logger to use both handlers
logging.basicConfig(
    level=logging.INFO,
    handlers=[console_handler, file_handler],
)

# =============================================================================
# OpenAI Setup
# =============================================================================

api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
    logging.error("OpenAI API key not found in environment variable 'OPENAI_API_KEY'. Please set it and rerun.")
    sys.exit(1)

client = OpenAI(api_key=api_key)

# =============================================================================
# Constants & Configuration
# =============================================================================

MAX_TOTAL_TOKENS = 4096
MAX_REFINEMENT_ATTEMPTS = 3
MAX_CHAT_HISTORY_TOKENS = 4096
RETRY_LIMIT = 3
RETRY_BACKOFF_FACTOR = 2
AGENTS_CONFIG_FILE = 'agents.json'

# Main multi-agent reasoning sessions are stored here
REASONING_HISTORY_FILE = 'reasoning_history.json'
# Swarm-based sessions are stored separately
SWARM_HISTORY_FILE = 'swarm_reasoning_history.json'

# =============================================================================
# Utility Functions for Saving & Retrieving Reasoning History
# =============================================================================

def append_session_record(file_path: str, record: dict):
    """
    Appends a single session record to a specified JSON file. Each file is treated as
    a list of session records. Uses ensure_ascii=False to preserve Unicode characters.

    Args:
        file_path (str): Path to the JSON file.
        record (dict): The session record to append.
    """
    if not os.path.exists(file_path):
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump([], f, indent=2, ensure_ascii=False)

    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
            if not isinstance(data, list):
                data = []
    except (json.JSONDecodeError, FileNotFoundError):
        data = []

    data.append(record)
    with open(file_path, 'w', encoding='utf-8') as f:
        json.dump(data, f, indent=2, ensure_ascii=False)

def append_reasoning_history(record: dict):
    """Appends a reasoning session record."""
    append_session_record(REASONING_HISTORY_FILE, record)

def append_swarm_history(record: dict):
    """Appends a swarm-based reasoning session record."""
    append_session_record(SWARM_HISTORY_FILE, record)

def load_reasoning_history_for_context(max_records=5, search_keywords=None):
    """
    Loads the last 'max_records' from 'reasoning_history.json', optionally
    searching for records that contain 'search_keywords'.
    Returns a list of summarized context strings.

    Args:
        max_records (int): Maximum number of records to retrieve.
        search_keywords (list, optional): Keywords to filter records.

    Returns:
        list: Summarized context strings.
    """
    contexts = []
    try:
        with open(REASONING_HISTORY_FILE, 'r', encoding='utf-8') as f:
            data = json.load(f)
            if not isinstance(data, list):
                return contexts
    except (json.JSONDecodeError, FileNotFoundError):
        return contexts

    # Reverse to start with the newest records
    data.reverse()
    count = 0
    for entry in data:
        if count >= max_records:
            break
        # If keywords are provided, perform a naive search
        if search_keywords:
            combined_text = (entry.get("user_prompt", "") + " " +
                             entry.get("final_response", "")).lower()
            if not any(kw.lower() in combined_text for kw in search_keywords):
                continue
        # Summarize the record
        summary = (f"Timestamp: {entry.get('timestamp')}\n"
                   f"User Prompt: {entry.get('user_prompt')}\n"
                   f"Final Response: {entry.get('final_response')}")
        contexts.append(summary)
        count += 1

    return contexts

def load_swarm_history_for_context(max_records=5, search_keywords=None):
    """
    Similar to 'load_reasoning_history_for_context' but for swarm-based history.

    Args:
        max_records (int): Maximum number of records to retrieve.
        search_keywords (list, optional): Keywords to filter records.

    Returns:
        list: Summarized context strings.
    """
    contexts = []
    try:
        with open(SWARM_HISTORY_FILE, 'r', encoding='utf-8') as f:
            data = json.load(f)
            if not isinstance(data, list):
                return contexts
    except (json.JSONDecodeError, FileNotFoundError):
        return contexts

    data.reverse()
    count = 0
    for entry in data:
        if count >= max_records:
            break
        if search_keywords:
            combined_text = (entry.get("user_prompt", "") + " " +
                             entry.get("final_response", "")).lower()
            if not any(kw.lower() in combined_text for kw in search_keywords):
                continue
        summary = (f"Timestamp: {entry.get('timestamp')}\n"
                   f"User Prompt: {entry.get('user_prompt')}\n"
                   f"Final Response: {entry.get('final_response')}")
        contexts.append(summary)
        count += 1

    return contexts

def get_local_context_for_prompt(user_prompt, is_swarm=False, max_records=3):
    """
    Fetches local 'memory' from either reasoning_history.json or swarm_reasoning_history.json,
    using naive keyword search on user_prompt. Returns a compiled context string to pass
    to the agent's instructions or prompt.

    Args:
        user_prompt (str): The user's input prompt.
        is_swarm (bool): Whether to fetch from swarm history.
        max_records (int): Maximum number of context records to retrieve.

    Returns:
        str: Combined context string or empty string if no context found.
    """
    # Extract simple keywords from user_prompt
    keywords = re.findall(r"\w+", user_prompt)

    if is_swarm:
        found_contexts = load_swarm_history_for_context(
            max_records=max_records,
            search_keywords=keywords
        )
    else:
        found_contexts = load_reasoning_history_for_context(
            max_records=max_records,
            search_keywords=keywords
        )

    if not found_contexts:
        return ""

    # Combine contexts into a single text block
    combined = "\n\n--- Retrieved Local Context ---\n\n"
    combined += "\n\n".join(found_contexts)
    return combined

# =============================================================================
# Agent Configuration
# =============================================================================

def load_agents_config():
    """
    Loads agent configurations from the 'agents.json' file.

    Returns:
        list: A list of agent configurations.
    """
    try:
        with open(AGENTS_CONFIG_FILE, 'r', encoding='utf-8') as f:
            agents_data = json.load(f)
        print(Fore.YELLOW + f"Successfully loaded agents configuration from '{AGENTS_CONFIG_FILE}'." + Style.RESET_ALL)
        return agents_data.get('agents', [])
    except FileNotFoundError:
        print(Fore.YELLOW + f"Agents configuration file '{AGENTS_CONFIG_FILE}' not found." + Style.RESET_ALL)
        logging.error(f"Agents configuration file '{AGENTS_CONFIG_FILE}' not found.")
        return []
    except json.JSONDecodeError as e:
        print(Fore.YELLOW + f"Error parsing '{AGENTS_CONFIG_FILE}': {e}" + Style.RESET_ALL)
        logging.error(f"Error parsing '{AGENTS_CONFIG_FILE}': {e}")
        return []

def get_shared_system_message():
    """
    Provides a shared system message for all agents to optimize prompt caching.

    Returns:
        str: The shared system message.
    """
    system_message = """
Your name is AI Assistant. You are a highly knowledgeable AI language model developed
to assist users with a wide range of tasks, including answering questions, providing
explanations, and offering insights across various domains.

As an AI, you possess in-depth understanding in fields such as:
1. Science and Technology
2. Mathematics
3. Humanities and Social Sciences
4. Arts and Literature
5. Current Events and General Knowledge
6. Languages and Communication
7. Ethics and Morality
8. Problem-Solving Skills
9. Logical Programming and Analysis
10. Creativity and Innovation

Guidelines for Interaction:
- Clarity: Provide clear and understandable explanations.
- Conciseness: Be concise and address the user's question directly.
- Neutrality: Maintain an unbiased stance.
- Confidentiality: Protect user privacy.
- Personable: Be personable and engaging in your responses.
- Use local memory to enhance responses to user prompts and improve conversation.
- Allow agents to ask each other for help if they are unsure about a topic.

This system message is consistent across all agents to optimize prompt caching.
    """
    return system_message

# =============================================================================
# Agent Class Definition
# =============================================================================

class Agent:
    """
    Represents an AI assistant agent with specific capabilities and interaction styles.
    """
    ACTION_DESCRIPTIONS = {
        'discuss':  "formulating a response",
        'verify':   "verifying data",
        'refine':   "refining the response",
        'critique': "critiquing another agent's response"
    }

    def __init__(self, color, **kwargs):
        self.name = kwargs.get('name', 'AI Assistant')
        self.color = color
        self.messages = []
        self.chat_history = []
        self.system_purpose = kwargs.get('system_purpose', '')

        additional_attributes = {
            k: v
            for k, v in kwargs.items()
            if k not in ['name', 'system_purpose', 'color']
        }
        self.instructions = self.system_purpose
        for attr_name, attr_value in additional_attributes.items():
            if isinstance(attr_value, dict):
                details = "\n".join(f"{kk.replace('_', ' ').title()}: {vv}" for kk, vv in attr_value.items())
                self.instructions += f"\n\n{attr_name.replace('_', ' ').title()}:\n{details}"
            else:
                self.instructions += f"\n\n{attr_name.replace('_', ' ').title()}: {attr_value}"

    def _add_message(self, role, content, mode='reasoning'):
        """
        Adds a message to the agent's message history and manages token limits.

        Args:
            role (str): The role of the message sender ('user', 'assistant').
            content (str): The message content.
            mode (str): The mode of operation ('reasoning' or 'chat').
        """
        try:
            encoding = tiktoken.get_encoding("cl100k_base")
        except Exception as e:
            logging.error(f"Error getting encoding: {e}")
            raise e

        if mode == 'chat':
            self.chat_history.append({"role": role, "content": content})
            total_tokens = sum(len(encoding.encode(msg['content'])) for msg in self.chat_history)
            while total_tokens > MAX_CHAT_HISTORY_TOKENS and len(self.chat_history) > 1:
                self.chat_history.pop(0)
                total_tokens = sum(len(encoding.encode(msg['content'])) for msg in self.chat_history)
        else:
            self.messages.append({"role": role, "content": content})
            total_tokens = sum(len(encoding.encode(msg['content'])) for msg in self.messages)
            while total_tokens > MAX_TOTAL_TOKENS and len(self.messages) > 1:
                self.messages.pop(0)
                total_tokens = sum(len(encoding.encode(msg['content'])) for msg in self.messages)

    def _handle_reasoning_logic(self, prompt):
        """
        Handles generating a response from the OpenAI API in non-chat mode.

        Args:
            prompt (str): The prompt to send to the API.

        Returns:
            tuple: (assistant_reply, duration)
        """
        shared_system = get_shared_system_message()
        system_message = f"{shared_system}\n\n{self.instructions}"

        messages = [{"role": "user", "content": system_message}]
        messages.extend(self.messages)
        messages.append({"role": "user", "content": prompt})

        start_time = time.time()
        retries = 0
        backoff = 1

        while retries < RETRY_LIMIT:
            try:
                response = client.chat.completions.create(
                    model="o1-2024-12-17",  # Adjust your model name here
                    messages=messages
                )
                end_time = time.time()
                duration = end_time - start_time

                assistant_reply = response.choices[0].message.content.strip()
                self._add_message("assistant", assistant_reply)

                usage = getattr(response, 'usage', None)
                if usage:
                    # Use safe getattr calls to avoid .get
                    prompt_tokens = getattr(usage, 'prompt_tokens', 0)
                    completion_tokens = getattr(usage, 'completion_tokens', 0)
                    total_tokens = getattr(usage, 'total_tokens', 0)

                    prompt_tokens_details = getattr(usage, 'prompt_tokens_details', None)
                    if prompt_tokens_details:
                        cached_tokens = getattr(prompt_tokens_details, 'cached_tokens', 0)
                    else:
                        cached_tokens = 0

                    completion_tokens_details = getattr(usage, 'completion_tokens_details', None)
                    if completion_tokens_details:
                        reasoning_tokens = getattr(completion_tokens_details, 'reasoning_tokens', 0)
                    else:
                        reasoning_tokens = 0

                    print(self.color + f"{self.name} used {cached_tokens} cached tokens out of {prompt_tokens} prompt tokens." + Style.RESET_ALL)
                    print(self.color + f"{self.name} generated {completion_tokens} completion tokens, including {reasoning_tokens} reasoning tokens. Total tokens used: {total_tokens}." + Style.RESET_ALL)
                else:
                    print(self.color + f"{self.name} (No usage details returned.)" + Style.RESET_ALL)

                return assistant_reply, duration
            except Exception as e:
                error_type = type(e).__name__
                logging.error(f"Error in agent '{self.name}' reasoning: {error_type}: {e}")
                retries += 1
                if retries >= RETRY_LIMIT:
                    logging.error(f"Agent '{self.name}' reached maximum retry limit.")
                    break
                backoff_time = backoff * (RETRY_BACKOFF_FACTOR ** (retries - 1))
                logging.info(f"Retrying in {backoff_time} seconds...")
                time.sleep(backoff_time)

        return "An error occurred while generating a response.", time.time() - start_time

    def _handle_chat_interaction(self, user_message):
        """
        Handles generating a response from the OpenAI API in chat mode.

        Args:
            user_message (str): The user's message.

        Returns:
            tuple: (assistant_reply, duration)
        """
        shared_system = get_shared_system_message()
        system_message = f"{shared_system}\n\n{self.instructions}"

        messages = [{"role": "user", "content": system_message}]
        messages.extend(self.chat_history)
        messages.append({"role": "user", "content": user_message})

        start_time = time.time()
        retries = 0
        backoff = 1

        while retries < RETRY_LIMIT:
            try:
                response = client.chat.completions.create(
                    model="gpt-4o",  # Use of gpt-4o model for chat interaction
                    messages=messages
                )
                end_time = time.time()
                duration = end_time - start_time

                assistant_reply = response.choices[0].message.content.strip()
                self._add_message("assistant", assistant_reply, mode='chat')

                usage = getattr(response, 'usage', None)
                if usage:
                    prompt_tokens = getattr(usage, 'prompt_tokens', 0)
                    completion_tokens = getattr(usage, 'completion_tokens', 0)
                    total_tokens = getattr(usage, 'total_tokens', 0)

                    prompt_tokens_details = getattr(usage, 'prompt_tokens_details', None)
                    if prompt_tokens_details:
                        cached_tokens = getattr(prompt_tokens_details, 'cached_tokens', 0)
                    else:
                        cached_tokens = 0

                    completion_tokens_details = getattr(usage, 'completion_tokens_details', None)
                    if completion_tokens_details:
                        reasoning_tokens = getattr(completion_tokens_details, 'reasoning_tokens', 0)
                    else:
                        reasoning_tokens = 0

                    print(self.color + f"{self.name} used {cached_tokens} cached tokens out of {prompt_tokens} prompt tokens." + Style.RESET_ALL)
                    print(self.color + f"{self.name} generated {completion_tokens} completion tokens, including {reasoning_tokens} reasoning tokens. Total tokens used: {total_tokens}." + Style.RESET_ALL)
                else:
                    print(self.color + f"{self.name} (No usage details returned.)" + Style.RESET_ALL)

                return assistant_reply, duration
            except Exception as e:
                error_type = type(e).__name__
                logging.error(f"Error in chat with agent '{self.name}': {error_type}: {e}")
                retries += 1
                if retries >= RETRY_LIMIT:
                    logging.error(f"Agent '{self.name}' reached maximum retry limit in chat.")
                    break
                backoff_time = backoff * (RETRY_BACKOFF_FACTOR ** (retries - 1))
                logging.info(f"Retrying chat in {backoff_time} seconds...")
                time.sleep(backoff_time)

        return "An error occurred while generating a response.", time.time() - start_time

    # =========================================================================
    # Public Actions
    # =========================================================================

    def discuss(self, prompt):
        """
        Initiates a discussion based on the given prompt.

        Args:
            prompt (str): The discussion prompt.

        Returns:
            tuple: (response, duration)
        """
        return self._handle_reasoning_logic(prompt)

    def verify(self, data):
        """
        Verifies the accuracy of the provided data.

        Args:
            data (str): The data to verify.

        Returns:
            tuple: (verification_result, duration)
        """
        verification_prompt = f"Verify the accuracy of the following information:\n\n{data}"
        return self._handle_reasoning_logic(verification_prompt)

    def refine(self, data, more_time=False, iterations=2):
        """
        Refines the provided data to improve its accuracy and completeness.

        Args:
            data (str): The data to refine.
            more_time (bool): Whether to allow more time for refinement.
            iterations (int): Number of refinement iterations.

        Returns:
            tuple: (refined_response, total_duration)
        """
        refinement_prompt = f"Please refine the following response to improve its accuracy and completeness:\n\n{data}"
        if more_time:
            refinement_prompt += "\nTake additional time to improve the response thoroughly."

        total_duration = 0
        refined_response = data
        for _ in range(iterations):
            refined_response, duration = self._handle_reasoning_logic(refinement_prompt)
            total_duration += duration
            # For the next iteration, feed the previously refined response
            refinement_prompt = f"Please further refine the following response:\n\n{refined_response}"

        return refined_response, total_duration

    def critique(self, other_agent_response):
        """
        Critiques another agent's response for accuracy and completeness.

        Args:
            other_agent_response (str): The response to critique.

        Returns:
            tuple: (critique_result, duration)
        """
        critique_prompt = f"Critique the following response for accuracy and completeness:\n\n{other_agent_response}"
        return self._handle_reasoning_logic(critique_prompt)

    # =========================================================================
    # Minimal "Agent-to-Agent" Helper
    # =========================================================================

    def ask_other_agent(self, other_agent, question):
        """
        Allows one agent to query another agent for assistance.

        Args:
            other_agent (Agent): The agent to ask the question.
            question (str): The question to ask.

        Returns:
            str: The other agent's response.
        """
        print(f"\n{self.color}{self.name}{Style.RESET_ALL} asks {other_agent.color}{other_agent.name}{Style.RESET_ALL}: {question}")
        response, _ = other_agent.discuss(question)
        return response

# =============================================================================
# Agent Initialization
# =============================================================================

def initialize_agents():
    """
    Initializes agents based on the configuration from 'agents.json'.
    If no configuration is found, default agents are used.

    Returns:
        list: A list of initialized Agent instances.
    """
    agents_data = load_agents_config()
    agents = []
    agent_data_dict = {}

    if not agents_data:
        print(Fore.YELLOW + "No agents found in the configuration. Using default agents." + Style.RESET_ALL)
        agent_a_data = {
            'name': 'Agent 47',
            'system_purpose': 'You are a logical and analytical assistant.',
            'personality': {'logical': 'Yes', 'analytical': 'Yes'},
        }
        agent_b_data = {
            'name': 'Agent 74',
            'system_purpose': 'You are a creative and empathetic assistant.',
            'personality': {'creative': 'Yes', 'empathetic': 'Yes'},
        }
        agent_a = Agent(Fore.MAGENTA, **agent_a_data)
        agent_b = Agent(Fore.CYAN, **agent_b_data)
        agents = [agent_a, agent_b]
    else:
        print(Fore.YELLOW + "Available agents:" + Style.RESET_ALL)
        agent_colors = {
            "Agent 47":     Fore.MAGENTA,
            "Agent 74":     Fore.CYAN,
            "Swarm Agent":  Fore.LIGHTGREEN_EX,
        }
        for agent_data in agents_data:
            name = agent_data.get('name', 'Unnamed Agent')
            color = agent_colors.get(name, Fore.WHITE)
            print(color + f"- {name}" + Style.RESET_ALL)

            agent = Agent(color, **agent_data)
            agents.append(agent)
            agent_data_dict[name] = agent_data

        # Inform agents about the other agents
        for agent in agents:
            other_agents_info = ""
            for other_agent in agents:
                if other_agent.name != agent.name:
                    info = f"Name: {other_agent.name}"
                    other_agent_data = agent_data_dict[other_agent.name]
                    system_purpose = other_agent_data.get('system_purpose', '')
                    info += f"\nSystem Purpose: {system_purpose}"
                    other_attributes = {
                        k: v
                        for k, v in other_agent_data.items()
                        if k not in ['name', 'system_purpose']
                    }
                    for attr_name, attr_value in other_attributes.items():
                        if isinstance(attr_value, dict):
                            details = "\n".join(
                                f"{ak.replace('_', ' ').title()}: {av}"
                                for ak, av in attr_value.items()
                            )
                            info += f"\n{attr_name.replace('_',' ').title()}:\n{details}"
                        else:
                            info += f"\n{attr_name.replace('_',' ').title()}: {attr_value}"
                    other_agents_info += f"\n\n{info}"
            agent.instructions += f"\n\nYou are aware of the following other agents:\n{other_agents_info.strip()}"

    return agents

# =============================================================================
# Blending Logic
# =============================================================================

def blend_responses(agent_responses, user_prompt):
    """
    Combines multiple agent responses into a single, optimal response.

    Args:
        agent_responses (list of tuples): List containing (agent_name, response) pairs.
        user_prompt (str): The original user prompt.

    Returns:
        str: The blended optimal response.
    """
    combined_prompt = (
        "Please combine the following responses into a single, optimal answer to the question.\n"
        f"Question: '{user_prompt}'\n"
        "Responses:\n"
        + "\n\n".join(f"Response from {agent_name}:\n{response}" for agent_name, response in agent_responses)
        + "\n\nProvide a concise and accurate combined response."
    )

    try:
        response = client.chat.completions.create(
            model="o1-2024-12-17",  # Adjust your model name here
            messages=[{"role": "user", "content": combined_prompt}]
        )

        blended_reply = response.choices[0].message.content.strip()

        usage = getattr(response, 'usage', None)
        if usage:
            prompt_tokens = getattr(usage, 'prompt_tokens', 0)
            completion_tokens = getattr(usage, 'completion_tokens', 0)
            total_tokens = getattr(usage, 'total_tokens', 0)

            prompt_tokens_details = getattr(usage, 'prompt_tokens_details', None)
            if prompt_tokens_details:
                cached_tokens = getattr(prompt_tokens_details, 'cached_tokens', 0)
            else:
                cached_tokens = 0

            completion_tokens_details = getattr(usage, 'completion_tokens_details', None)
            if completion_tokens_details:
                reasoning_tokens = getattr(completion_tokens_details, 'reasoning_tokens', 0)
            else:
                reasoning_tokens = 0

            print(Fore.GREEN + f"Blending used {cached_tokens} cached tokens out of {prompt_tokens} prompt tokens." + Style.RESET_ALL)
            print(Fore.GREEN + f"Blending generated {completion_tokens} completion tokens, including {reasoning_tokens} reasoning tokens. Total tokens used: {total_tokens}." + Style.RESET_ALL)
        else:
            print(Fore.GREEN + "(No usage details returned for blending.)" + Style.RESET_ALL)

        return blended_reply
    except Exception as e:
        logging.error(f"Error in blending responses: {e}")
        return "An error occurred while attempting to blend responses."

# =============================================================================
# Console Utilities
# =============================================================================

def print_divider(char="═", length=100, color=Fore.YELLOW):
    """
    Prints a divider line of specified character, length, and color.
    """
    print(color + (char * length) + Style.RESET_ALL)

def print_header(title, color=Fore.YELLOW):
    """
    Prints a formatted header with a box around the title text.
    """
    border = "═" * 58
    print(color + f"╔{border}╗")
    print(color + f"║{title.center(58)}║")
    print(color + f"╚{border}╝" + Style.RESET_ALL)

def process_agent_action(agent, action, *args, **kwargs):
    """
    Processes an action (discuss, verify, refine, critique) for a given agent.

    Args:
        agent (Agent): The agent performing the action.
        action (str): The action to perform.
        *args: Positional arguments for the action method.
        **kwargs: Keyword arguments for the action method.

    Returns:
        tuple: (result_text, duration)
    """
    action_method = getattr(agent, action, None)
    if not action_method:
        logging.error(f"Action '{action}' not found for agent '{agent.name}'.")
        return "Invalid action.", 0

    action_description = agent.ACTION_DESCRIPTIONS.get(action, "performing an action")

    print_divider()
    print(Fore.YELLOW + f"System Message: {agent.color}{agent.name} is {action_description}..." + Style.RESET_ALL)

    try:
        result, duration = action_method(*args, **kwargs)
        if result:
            print(agent.color + f"\n=== {agent.name} {action.capitalize()} Output ===" + Style.RESET_ALL)
            print(agent.color + result + Style.RESET_ALL)
        print(agent.color + f"{agent.name}'s action completed in {duration:.2f} seconds." + Style.RESET_ALL)
        return result, duration
    except Exception as e:
        logging.error(f"Error during {action} action for {agent.name}: {e}")
        return "An error occurred.", 0

def handle_special_commands(user_input, agents):
    """
    Handles special user commands: 'exit', 'history', 'clear'.

    Args:
        user_input (str): The user's input command.
        agents (list): List of Agent instances.

    Returns:
        bool: True if a special command was handled, False otherwise.
    """
    cmd = user_input.strip().lower()
    if cmd == 'exit':
        print(Fore.YELLOW + "Goodbye!" + Style.RESET_ALL)
        sys.exit(0)
    elif cmd == 'history':
        print(Fore.YELLOW + "\nConversation History:" + Style.RESET_ALL)
        for agent in agents:
            print(agent.color + f"\n{agent.name} Conversation:" + Style.RESET_ALL)
            for msg in agent.messages:
                print(f"{msg['role'].capitalize()}: {msg['content']}")
        return True
    elif cmd == 'clear':
        for agent in agents:
            agent.messages.clear()
            agent.chat_history.clear()
        print(Fore.YELLOW + "Conversation history cleared." + Style.RESET_ALL)
        return True
    return False

# =============================================================================
# Chat Logic (with local memory retrieval)
# =============================================================================

def chat_with_agents(agents):
    """
    Facilitates chat interactions between the user and selected agents.

    Args:
        agents (list): List of Agent instances.
    """
    while True:
        print(Fore.YELLOW + "Available agents to chat with:" + Style.RESET_ALL)
        for idx, agent in enumerate(agents, 1):
            print(f"{idx}. {agent.color}{agent.name}{Style.RESET_ALL}")

        print(Fore.YELLOW + "Enter the number of the agent to chat with, or 'menu' to return, or 'exit' to exit program: " + Style.RESET_ALL, end='')
        selection = input().strip().lower()

        if selection == 'menu':
            return
        if selection == 'exit':
            print(Fore.YELLOW + "Goodbye!" + Style.RESET_ALL)
            sys.exit(0)

        if selection.isdigit() and 1 <= int(selection) <= len(agents):
            selected_agent = agents[int(selection) - 1]
        else:
            print(Fore.YELLOW + f"Invalid selection. Please enter a number between 1 and {len(agents)}, 'menu', or 'exit'." + Style.RESET_ALL)
            continue

        print(Fore.YELLOW + f"Starting chat with {selected_agent.color}{selected_agent.name}{Style.RESET_ALL}.")
        print(Fore.YELLOW + "Type 'menu' to return to agent selection or 'exit' to end the program." + Style.RESET_ALL)

        while True:
            print(Fore.YELLOW + "\nYou (type 'menu' or 'exit'): " + Style.RESET_ALL, end='')
            user_message = input().strip()

            if user_message.lower() == 'menu':
                print(Fore.YELLOW + "Returning to agent selection menu..." + Style.RESET_ALL)
                break
            if user_message.lower() == 'exit':
                print(Fore.YELLOW + "Goodbye!" + Style.RESET_ALL)
                sys.exit(0)

            # Retrieve local context from reasoning_history.json
            local_context = get_local_context_for_prompt(user_message, is_swarm=False)
            user_message_with_context = f"{user_message}\n\n{local_context}" if local_context else user_message

            # Handle special commands
            if handle_special_commands(user_message, [selected_agent]):
                continue

            assistant_reply, duration = selected_agent._handle_chat_interaction(user_message_with_context)
            print(selected_agent.color + f"{selected_agent.name}: {assistant_reply}" + Style.RESET_ALL)

# =============================================================================
# Reasoning Logic (with local memory + agent-to-agent help)
# =============================================================================

def reasoning_logic(agents):
    """
    Handles the reasoning workflow, which includes discussing, verifying, critiquing,
    refining, and blending responses from multiple agents.

    Args:
        agents (list): A list of Agent instances.
    """
    while True:
        print(Fore.YELLOW + "Please enter your prompt (or type 'menu' to return, 'exit' to quit): " + Style.RESET_ALL, end='')
        user_prompt = input().strip()

        if user_prompt.lower() == 'menu':
            print(Fore.YELLOW + "Returning to main menu." + Style.RESET_ALL)
            break
        if user_prompt.lower() == 'exit':
            print(Fore.YELLOW + "Goodbye!" + Style.RESET_ALL)
            sys.exit(0)

        # Handle special commands
        if handle_special_commands(user_prompt, agents):
            continue

        if len(user_prompt) <= 4:
            print(Fore.YELLOW + "Your prompt must be more than 4 characters. Please try again." + Style.RESET_ALL)
            continue

        # Retrieve local memory relevant to user_prompt
        local_context = get_local_context_for_prompt(user_prompt, is_swarm=False, max_records=3)

        # Incorporate context into the user prompt if available
        extended_prompt = f"{user_prompt}\n\n--- Additional local memory context ---\n{local_context}" if local_context else user_prompt

        # ============ Step 1: Discuss ============
        print_header("Reasoning Step 1: Discussing the Prompt")
        opinions = {}
        durations = {}
        for agent in agents:
            # Example: Agent can ask another agent for help based on specific keyword
            if "ask-other" in extended_prompt.lower() and len(agents) > 1:
                helper_agent = agents[(agents.index(agent) + 1) % len(agents)]
                help_response = agent.ask_other_agent(helper_agent, "Do you have any insights on this topic?")
                full_opinion_prompt = f"{extended_prompt}\nHelper agent says: {help_response}"
            else:
                full_opinion_prompt = extended_prompt

            opinion, duration = process_agent_action(agent, 'discuss', full_opinion_prompt)
            opinions[agent.name] = opinion
            durations[agent.name] = duration

        total_discussion_time = sum(durations.values())
        print_divider()
        print(Fore.YELLOW + f"Total discussion time: {total_discussion_time:.2f} seconds." + Style.RESET_ALL)

        # ============ Step 2: Verify ============
        print_header("Reasoning Step 2: Verifying Responses")
        verified_opinions = {}
        verify_durations = {}

        with ThreadPoolExecutor() as executor:
            futures = {executor.submit(process_agent_action, agent, 'verify', opinions[agent.name]): agent for agent in agents}
            for future in futures:
                agent = futures[future]
                verified_opinion, duration = future.result()
                verified_opinions[agent.name] = verified_opinion
                verify_durations[agent.name] = duration

        total_verification_time = sum(verify_durations.values())
        print_divider()
        print(Fore.YELLOW + f"Total verification time: {total_verification_time:.2f} seconds." + Style.RESET_ALL)

        # ============ Step 3: Critique ============
        print_header("Reasoning Step 3: Critiquing Responses")
        critiques = {}
        critique_durations = {}
        num_agents = len(agents)
        for i, agent in enumerate(agents):
            other_agent = agents[(i + 1) % num_agents]
            critique, duration = process_agent_action(agent, 'critique', verified_opinions[other_agent.name])
            critiques[agent.name] = critique
            critique_durations[agent.name] = duration

        total_critique_time = sum(critique_durations.values())
        print_divider()
        print(Fore.YELLOW + f"Total critique time: {total_critique_time:.2f} seconds." + Style.RESET_ALL)

        # ============ Step 4: Refine ============
        print_header("Reasoning Step 4: Refining Responses")
        refined_opinions = {}
        refine_durations = {}
        for agent in agents:
            refined_opinion, duration = process_agent_action(agent, 'refine', opinions[agent.name])
            refined_opinions[agent.name] = refined_opinion
            refine_durations[agent.name] = duration

        total_refinement_time = sum(refine_durations.values())
        print_divider()
        print(Fore.YELLOW + f"Total refinement time: {total_refinement_time:.2f} seconds." + Style.RESET_ALL)

        # ============ Step 5: Blend ============
        print_header("Reasoning Step 5: Blending Responses")
        agent_responses = [(agent.name, refined_opinions[agent.name]) for agent in agents]
        start_blend_time = time.time()
        optimal_response = blend_responses(agent_responses, user_prompt)
        end_blend_time = time.time()
        blend_duration = end_blend_time - start_blend_time

        print_divider()
        print_header("Optimal Response")
        print(Fore.GREEN + optimal_response + Style.RESET_ALL)
        print_divider()
        print(Fore.YELLOW + f"Response generated in {blend_duration:.2f} seconds." + Style.RESET_ALL)

        # ======= Feedback Loop ========
        refine_count = 0
        more_time = False
        user_feedback = None
        while refine_count < MAX_REFINEMENT_ATTEMPTS:
            print(Fore.YELLOW + "\nWas this response helpful and accurate? (yes/no, 'menu' to main menu, 'exit' to quit): " + Style.RESET_ALL, end='')
            user_feedback = input().strip().lower()

            if user_feedback == 'menu':
                print(Fore.YELLOW + "Returning to main menu." + Style.RESET_ALL)
                save_reasoning_session(user_prompt, optimal_response, user_feedback, context_retained=False)
                return
            if user_feedback == 'exit':
                print(Fore.YELLOW + "Goodbye!" + Style.RESET_ALL)
                save_reasoning_session(user_prompt, optimal_response, user_feedback, context_retained=False)
                sys.exit(0)

            if user_feedback == 'yes':
                print(Fore.YELLOW + "Thank you for your feedback!" + Style.RESET_ALL)
                break
            elif user_feedback != 'no':
                print(Fore.YELLOW + "Please answer 'yes', 'no', 'menu' or 'exit'." + Style.RESET_ALL)
                continue

            # If user says no, attempt to refine again
            refine_count += 1
            if refine_count >= 2:
                print(Fore.YELLOW + "Would you like the agents to take more time refining the response? (yes/no): " + Style.RESET_ALL, end='')
                more_time_input = input().strip().lower()
                more_time = (more_time_input == 'yes')

            print(Fore.YELLOW + "We're sorry to hear that. Let's try to improve the response." + Style.RESET_ALL)

            for agent in agents:
                refined_opinion, duration = process_agent_action(
                    agent, 'refine',
                    refined_opinions[agent.name],
                    more_time=more_time
                )
                refined_opinions[agent.name] = refined_opinion
                refine_durations[agent.name] += duration

            total_refinement_time = sum(refine_durations.values())
            print_divider()
            print(Fore.YELLOW + f"Total refinement time: {total_refinement_time:.2f} seconds." + Style.RESET_ALL)

            # Re-blend the refined responses
            print_divider()
            print_header("Blending Refined Responses")
            agent_responses = [(agent.name, refined_opinions[agent.name]) for agent in agents]
            start_blend_time = time.time()
            optimal_response = blend_responses(agent_responses, user_prompt)
            end_blend_time = time.time()
            blend_duration = end_blend_time - start_blend_time

            print_divider()
            print_header("New Optimal Response")
            print(Fore.GREEN + optimal_response + Style.RESET_ALL)
            print_divider()
            print(Fore.YELLOW + f"Response generated in {blend_duration:.2f} seconds." + Style.RESET_ALL)

        else:
            print(Fore.YELLOW + "Maximum refinement attempts reached." + Style.RESET_ALL)

        if not user_feedback:
            user_feedback = "no"

        print(Fore.YELLOW + "Would you like to retain this conversation context for the next prompt? (yes/no): " + Style.RESET_ALL, end='')
        retain_context_input = input().strip().lower()
        context_retained = (retain_context_input == 'yes')
        if not context_retained:
            for agent in agents:
                agent.messages.clear()
            print(Fore.YELLOW + "Conversation context has been reset." + Style.RESET_ALL)
        else:
            print(Fore.YELLOW + "Conversation context has been retained for the next prompt." + Style.RESET_ALL)

        # Save final session
        save_reasoning_session(user_prompt, optimal_response, user_feedback, context_retained)

# =============================================================================
# Save Reasoning Session
# =============================================================================

def save_reasoning_session(user_prompt, final_response, user_feedback, context_retained):
    """
    Saves the reasoning session details to the history file.

    Args:
        user_prompt (str): The user's prompt.
        final_response (str): The final response generated.
        user_feedback (str): The user's feedback ('yes'/'no').
        context_retained (bool): Whether the context is retained.
    """
    record = {
        "timestamp": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
        "user_prompt": user_prompt,
        "final_response": final_response,
        "user_feedback": user_feedback,
        "context_retained": context_retained
    }
    append_reasoning_history(record)
    logging.info("Reasoning session appended to reasoning_history.json.")

# =============================================================================
# Swarm Reasoning Feedback (with local memory as well)
# =============================================================================

def swarm_reasoning_feedback(user_prompt, final_response):
    """
    Handles user feedback for swarm-based reasoning and saves the session.

    Args:
        user_prompt (str): The user's prompt.
        final_response (str): The response generated by the swarm.
    """
    user_feedback = None
    while True:
        print(Fore.YELLOW + "\nWas this response helpful and accurate? (yes/no, 'menu' to main menu, 'exit' to quit): " + Style.RESET_ALL, end='')
        user_feedback = input().strip().lower()

        if user_feedback == 'menu':
            print(Fore.YELLOW + "Returning to main menu." + Style.RESET_ALL)
            save_swarm_session(user_prompt, final_response, user_feedback, context_retained=False)
            return
        elif user_feedback == 'exit':
            print(Fore.YELLOW + "Goodbye!" + Style.RESET_ALL)
            save_swarm_session(user_prompt, final_response, user_feedback, context_retained=False)
            sys.exit(0)
        elif user_feedback == 'yes':
            print(Fore.YELLOW + "Thank you for your feedback!" + Style.RESET_ALL)
            break
        elif user_feedback != 'no':
            print(Fore.YELLOW + "Please answer 'yes', 'no', 'menu' or 'exit'." + Style.RESET_ALL)
            continue
        else:
            print(Fore.YELLOW + "Sorry to hear that. (Swarm refining is not yet implemented.)" + Style.RESET_ALL)
            break

    print(Fore.YELLOW + "Would you like to retain this conversation context for the next prompt? (yes/no): " + Style.RESET_ALL, end='')
    retain_context_input = input().strip().lower()
    context_retained = (retain_context_input == 'yes')
    if not context_retained:
        print(Fore.YELLOW + "Swarm conversation context has been reset." + Style.RESET_ALL)
    else:
        print(Fore.YELLOW + "Swarm conversation context has been retained for the next prompt." + Style.RESET_ALL)

    # Save swarm session
    save_swarm_session(user_prompt, final_response, user_feedback, context_retained)

def save_swarm_session(user_prompt, final_response, user_feedback, context_retained):
    """
    Saves the swarm-based reasoning session details to the history file.

    Args:
        user_prompt (str): The user's prompt.
        final_response (str): The response generated by the swarm.
        user_feedback (str): The user's feedback ('yes'/'no').
        context_retained (bool): Whether the context is retained.
    """
    record = {
        "timestamp": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
        "user_prompt": user_prompt,
        "final_response": final_response,
        "user_feedback": user_feedback,
        "context_retained": context_retained
    }
    append_swarm_history(record)
    logging.info("Swarm-based session appended to swarm_reasoning_history.json.")

# =============================================================================
# Main Menu
# =============================================================================

def main_menu():
    """
    Displays the main menu and handles user navigation between different modes:
      1) Chat with an agent (single-agent chat)
      2) Use reasoning logic (multi-step discussion, verify, critique, refine, blend)
      3) Swarm-based reasoning (using the swarm_middle_agent_interface)
      4) Exit the program

    The user remains in each mode until they explicitly type 'menu' (to go back to this menu)
    or 'exit' (to end the program).
    """
    agents = initialize_agents()
    current_mode = None

    while True:
        # Only display the main menu if we're not in any current_mode
        if current_mode is None:
            print_divider()
            print_header("Multi-Agent Reasoning Chatbot")
            print(Fore.YELLOW + "Please select an option:" + Style.RESET_ALL)
            print("1. Chat with an agent")
            print("2. Use reasoning logic")
            print("3. Use Swarm-based reasoning")
            print("4. Exit")

            while True:
                print(Fore.YELLOW + "Enter your choice (1/2/3/4): " + Style.RESET_ALL, end='')
                choice = input().strip()
                if choice in ['1', '2', '3', '4']:
                    break
                else:
                    print(Fore.YELLOW + "Invalid choice. Please enter 1, 2, 3, or 4." + Style.RESET_ALL)

            if choice == '1':
                current_mode = 'chat'
            elif choice == '2':
                current_mode = 'reasoning'
            elif choice == '3':
                current_mode = 'swarm'
            elif choice == '4':
                print(Fore.YELLOW + "Goodbye!" + Style.RESET_ALL)
                sys.exit(0)

        # ----------------------------------------------------------------------
        # OPTION 1: Chat with an agent
        # ----------------------------------------------------------------------
        if current_mode == 'chat':
            # This function already handles a loop allowing the user to keep
            # chatting with the selected agent(s) until 'menu' or 'exit'.
            chat_with_agents(agents)
            current_mode = None

        # ----------------------------------------------------------------------
        # OPTION 2: Reasoning logic
        # ----------------------------------------------------------------------
        elif current_mode == 'reasoning':
            # This function runs the multi-step reasoning process in a loop.
            reasoning_logic(agents)
            current_mode = None

        # ----------------------------------------------------------------------
        # OPTION 3: Swarm-based reasoning
        # ----------------------------------------------------------------------
        elif current_mode == 'swarm':
            # We stay in swarm-based mode until user selects 'menu' or 'exit'.
            while True:
                print(Fore.YELLOW + 
                      "Enter your reasoning prompt for Swarm (or type 'menu' to return, 'exit' to quit): " +
                      Style.RESET_ALL, end='')
                user_prompt = input().strip()
                lower_prompt = user_prompt.lower()

                if lower_prompt == 'exit':
                    print(Fore.YELLOW + "Goodbye!" + Style.RESET_ALL)
                    sys.exit(0)
                elif lower_prompt == 'menu':
                    current_mode = None
                    break  # Return to the main menu

                # Retrieve local swarm memory for context
                swarm_context = get_local_context_for_prompt(
                    user_prompt,
                    is_swarm=True,
                    max_records=3
                )

                user_prompt_w_context = (
                    f"{user_prompt}\n\n--- Additional local swarm memory context ---\n{swarm_context}"
                    if swarm_context else user_prompt
                )

                # Hand off the prompt to the swarm middle agent
                final_swarm_response = swarm_middle_agent_interface(user_prompt_w_context)
                final_swarm_response = final_swarm_response or "No final swarm response captured."

                # Provide user feedback loop for the swarm response
                swarm_reasoning_feedback(user_prompt, final_swarm_response)

# =============================================================================
# Script Entry Point
# =============================================================================
if __name__ == "__main__":
    main_menu()


================================================
FILE: requirements.txt
================================================
openai
colorama
tiktoken
git+https://github.com/openai/swarm.git#egg=swarm

================================================
FILE: swarm_middle_agent.py
================================================
import os
import sys
import time
import logging
import json
from colorama import Fore, Style, init
from swarm import Agent, Swarm  # Ensure the 'swarm' package is installed

# Initialize colorama
init(autoreset=True)

# =============================================================================
# Logging Configuration
# =============================================================================

class ColoredFormatter(logging.Formatter):
    """
    Custom Formatter for Logging that applies color based on log level
    and specific keywords.
    """
    LEVEL_COLORS = {
        logging.DEBUG: Fore.LIGHTYELLOW_EX,
        logging.INFO: Fore.WHITE,
        logging.WARNING: Fore.YELLOW,
        logging.ERROR: Fore.RED,
        logging.CRITICAL: Fore.RED + Style.BRIGHT,
    }
    KEYWORD_COLORS = {
        'HTTP Request': Fore.LIGHTYELLOW_EX,
    }

    def format(self, record):
        message = super().format(record)
        # Apply color based on specific keywords
        for keyword, color in self.KEYWORD_COLORS.items():
            if keyword in message:
                return color + message + Style.RESET_ALL
        # Otherwise, color based on log level
        color = self.LEVEL_COLORS.get(record.levelno, Fore.WHITE)
        return color + message + Style.RESET_ALL

# Remove existing handlers to avoid duplicate logs
for handler in logging.root.handlers[:]:
    logging.root.removeHandler(handler)

# Create a console handler with the custom formatter
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_formatter = ColoredFormatter('%(asctime)s %(levelname)s:%(message)s')
console_handler.setFormatter(console_formatter)

# Create a file handler for general logging
file_handler = logging.FileHandler("swarm_middle_agent.log")
file_handler.setLevel(logging.INFO)
file_formatter = logging.Formatter('%(asctime)s %(levelname)s:%(message)s')
file_handler.setFormatter(file_formatter)

# Configure the root logger to use both handlers
logging.basicConfig(
    level=logging.INFO,
    handlers=[console_handler, file_handler],
)

# =============================================================================
# Swarm Client Initialization
# =============================================================================

def initialize_swarm_client():
    """
    Initializes the Swarm client.

    Returns:
        Swarm: An instance of the Swarm client.
    """
    try:
        client = Swarm()
        logging.info("Swarm client initialized successfully.")
        return client
    except Exception as e:
        logging.error(f"Failed to initialize Swarm client: {e}")
        sys.exit(1)

client = initialize_swarm_client()

# =============================================================================
# Constants & Agent Configuration
# =============================================================================

AGENTS_CONFIG_FILE = 'agents.json'

def load_agents_config():
    """
    Loads agent configurations from the 'agents.json' file.

    Returns:
        list: A list of agent configurations.
    """
    try:
        with open(AGENTS_CONFIG_FILE, 'r', encoding='utf-8') as f:
            data = json.load(f)
        logging.info(f"Successfully loaded agents configuration from '{AGENTS_CONFIG_FILE}'.")
        return data.get('agents', [])
    except FileNotFoundError:
        logging.error(f"Agents configuration file '{AGENTS_CONFIG_FILE}' not found.")
        return []
    except json.JSONDecodeError as e:
        logging.error(f"Error parsing '{AGENTS_CONFIG_FILE}': {e}")
        return []

# =============================================================================
# Utility Functions
# =============================================================================

def print_divider(char="═", length=100, color=Fore.YELLOW):
    """
    Prints a divider line of specified character, length, and color.

    Args:
        char (str): The character to use for the divider.
        length (int): The length of the divider.
        color (str): The color code from colorama.
    """
    print(color + (char * length) + Style.RESET_ALL)

def print_header(title, color=Fore.YELLOW):
    """
    Prints a formatted header with a box around the title text.

    Args:
        title (str): The header title.
        color (str): The color code from colorama.
    """
    border = "═" * 58
    print(color + f"\n╔{border}╗")
    print(color + f"║{title.center(58)}║")
    print(color + f"╚{border}╝" + Style.RESET_ALL)

# =============================================================================
# Swarm Agents Initialization
# =============================================================================

def initialize_swarm_agents():
    """
    Initializes Swarm-based agents from configuration.

    Returns:
        list: A list of initialized Swarm Agent instances.
    """
    agents_data = load_agents_config()
    if not agents_data:
        logging.error("No agents found in the configuration. Please check 'agents.json'.")
        sys.exit(1)

    agents = []
    agent_data_dict = {}

    for agent_data in agents_data:
        name = agent_data.get('name', 'Unnamed Agent')
        system_purpose = agent_data.get('system_purpose', '')
        additional_attrs = {
            k: v for k, v in agent_data.items()
            if k not in ['name', 'system_purpose']
        }

        # Build the instructions from system purpose + other attributes
        full_instructions = system_purpose
        for attr_name, attr_value in additional_attrs.items():
            if isinstance(attr_value, dict):
                details = "\n".join(
                    f"{ak.replace('_',' ').title()}: {av}" for ak, av in attr_value.items()
                )
                full_instructions += f"\n\n{attr_name.replace('_',' ').title()}:\n{details}"
            else:
                full_instructions += f"\n\n{attr_name.replace('_',' ').title()}: {attr_value}"

        swarm_agent = Agent(
            name=name,
            instructions=full_instructions
        )
        agents.append(swarm_agent)
        agent_data_dict[name] = agent_data

    # Inform agents about other agents
    for agent in agents:
        other_agents_info = ""
        for other_agent in agents:
            if other_agent.name != agent.name:
                info = f"Name: {other_agent.name}"
                o_data = agent_data_dict[other_agent.name]
                sp = o_data.get('system_purpose', '')
                info += f"\nSystem Purpose: {sp}"

                more_attrs = {
                    k: v for k, v in o_data.items()
                    if k not in ['name', 'system_purpose']
                }
                for attr_name, attr_value in more_attrs.items():
                    if isinstance(attr_value, dict):
                        details = "\n".join(
                            f"{ak.replace('_',' ').title()}: {av}" for ak, av in attr_value.items()
                        )
                        info += f"\n{attr_name.replace('_',' ').title()}:\n{details}"
                    else:
                        info += f"\n{attr_name.replace('_',' ').title()}: {attr_value}"
                other_agents_info += f"\n\n{info}"

        agent.instructions += (
            f"\n\nYou are aware of the following other agents:\n{other_agents_info.strip()}"
        )

    logging.info(f"Initialized {len(agents)} swarm agents.")
    return agents

# =============================================================================
# Swarm Reasoning Process
# =============================================================================

def run_swarm_reasoning(user_prompt):
    """
    Orchestrates a multi-agent 'Swarm' process to produce a refined or 
    consolidated answer to user_prompt, returning that final text.

    Args:
        user_prompt (str): The user's input prompt.

    Returns:
        str: The final blended response from the swarm.
    """
    agents = initialize_swarm_agents()
    num_agents = len(agents)
    opinions = {}
    verified_opinions = {}
    critiques = {}
    refined_opinions = {}

    print(Fore.YELLOW + "\nRunning Swarm-based reasoning...\n" + Style.RESET_ALL)

    # ------------------ Step 1: Discuss the Prompt ------------------
    print_header("Reasoning Step 1: Discussing the Prompt")
    for agent in agents:
        response = client.run(
            agent=agent,
            messages=[{"role": "user", "content": user_prompt}]
        )
        agent_opinion = response.messages[-1]['content']
        opinions[agent.name] = agent_opinion
        color = get_agent_color(agent.name)
        print(color + f"{agent.name} response: {agent_opinion}" + Style.RESET_ALL)

    # ------------------ Step 2: Verify the Responses ------------------
    print_header("Reasoning Step 2: Verifying Responses")
    for agent in agents:
        verify_prompt = (
            f"Please verify the accuracy of your previous response:\n\n{opinions[agent.name]}"
        )
        response = client.run(
            agent=agent,
            messages=[{"role": "user", "content": verify_prompt}]
        )
        verified_opinion = response.messages[-1]['content']
        verified_opinions[agent.name] = verified_opinion
        color = get_agent_color(agent.name)
        print(color + f"{agent.name} verified response: {verified_opinion}" + Style.RESET_ALL)

    # ------------------ Step 3: Critique Each Other ------------------
    print_header("Reasoning Step 3: Critiquing Responses")
    for i, agent in enumerate(agents):
        other_agent = agents[(i + 1) % num_agents]
        critique_prompt = (
            f"Please critique {other_agent.name}'s response "
            f"for depth and accuracy:\n\n{verified_opinions[other_agent.name]}"
        )
        response = client.run(
            agent=agent,
            messages=[{"role": "user", "content": critique_prompt}]
        )
        critique_text = response.messages[-1]['content']
        critiques[agent.name] = critique_text
        color = get_agent_color(agent.name)
        print(color + f"{agent.name} critique on {other_agent.name}:\n{critique_text}\n" + Style.RESET_ALL)

    # ------------------ Step 4: Refine the Responses ------------------
    print_header("Reasoning Step 4: Refining Responses")
    for i, agent in enumerate(agents):
        other_agent = agents[(i + 1) % num_agents]
        refine_prompt = (
            f"Please refine your response based on {other_agent.name}'s critique:\n\n"
            f"Your Original Response:\n{opinions[agent.name]}\n\n"
            f"{other_agent.name}'s Critique:\n{critiques[agent.name]}"
        )
        response = client.run(
            agent=agent,
            messages=[{"role": "user", "content": refine_prompt}]
        )
        refined_text = response.messages[-1]['content']
        refined_opinions[agent.name] = refined_text
        color = get_agent_color(agent.name)
        print(color + f"{agent.name} refined response: {refined_text}" + Style.RESET_ALL)

    # ------------------ Step 5: Blend Refined Responses ------------------
    print_header("Reasoning Step 5: Blending Responses")
    agent_responses = [(agent.name, refined_opinions[agent.name]) for agent in agents]
    final_blended_response = blend_responses(agent_responses, user_prompt)
    print(Fore.GREEN + f"\nFinal Blended Response:\n{final_blended_response}" + Style.RESET_ALL)
    print(Fore.GREEN + "\nSwarm-based reasoning completed.\n" + Style.RESET_ALL)

    return final_blended_response

def blend_responses(agent_responses, user_prompt):
    """
    Combines multiple agent responses into a single, optimal response via a specialized 'Swarm Agent'.

    Args:
        agent_responses (list of tuples): (agent_name, response)
        user_prompt (str): The original prompt from the user.

    Returns:
        str: The blended optimal response text.
    """
    combined_prompt = (
        "Please combine the following responses into a single, optimal answer to the question.\n"
        f"Question: '{user_prompt}'\n"
        "Responses:\n"
        + "\n\n".join(
            f"Response from {agent_name}:\n{response}" for agent_name, response in agent_responses
        )
        + "\n\nProvide a concise and accurate combined response."
    )

    try:
        blender_agent = Agent(
            name="Swarm Agent",
            instructions="You are a collaborative AI assistant composed of multiple expert agents."
        )

        response = client.run(
            agent=blender_agent,
            messages=[{"role": "user", "content": combined_prompt}]
        )
        blended_reply = response.messages[-1]['content'].strip()

        # Safely retrieve usage details
        usage = getattr(response, 'usage', None)
        if usage:
            # Instead of usage.get("prompt_tokens", 0), use getattr
            prompt_tokens = getattr(usage, 'prompt_tokens', 0)
            completion_tokens = getattr(usage, 'completion_tokens', 0)
            total_tokens = getattr(usage, 'total_tokens', 0)

            # For nested details
            prompt_tokens_details = getattr(usage, 'prompt_tokens_details', None)
            if prompt_tokens_details:
                cached_tokens = getattr(prompt_tokens_details, 'cached_tokens', 0)
            else:
                cached_tokens = 0

            completion_tokens_details = getattr(usage, 'completion_tokens_details', None)
            if completion_tokens_details:
                reasoning_tokens = getattr(completion_tokens_details, 'reasoning_tokens', 0)
            else:
                reasoning_tokens = 0

            logging.info(
                f"Blending usage -> Prompt: {prompt_tokens}, Completion: {completion_tokens}, "
                f"Total: {total_tokens}, Cached: {cached_tokens}, Reasoning: {reasoning_tokens}"
            )
        else:
            logging.info("No usage details returned for blending.")

        return blended_reply
    except Exception as e:
        logging.error(f"Error in blend_responses: {e}")
        return "An error occurred while attempting to blend responses."

def get_agent_color(agent_name):
    """
    Retrieves the color associated with a given agent.

    Args:
        agent_name (str): The name of the agent.

    Returns:
        str: The color code from colorama.
    """
    agent_colors = {
        "Agent 47":     Fore.MAGENTA,
        "Agent 74":     Fore.CYAN,
        "Swarm Agent":  Fore.LIGHTGREEN_EX,
    }
    return agent_colors.get(agent_name, Fore.WHITE)

# =============================================================================
# Interface Functions
# =============================================================================

def swarm_middle_agent_interface(user_prompt):
    """
    Interface function to trigger multi-stage swarm reasoning with a single call
    from external code (e.g., reasoning.py).
    Returns the final swarm-blended text.

    Args:
        user_prompt (str): The user's input prompt.

    Returns:
        str: The final swarm response or None if an error occurred.
    """
    try:
        start_time = time.time()
        final_text = run_swarm_reasoning(user_prompt)
        end_time = time.time()
        logging.info(f"Swarm reasoning completed in {end_time - start_time:.2f} seconds.")
        return final_text
    except Exception as e:
        logging.error(f"Error in swarm_middle_agent_interface: {e}")
        return None

# =============================================================================
# Main (Optional Test)
# =============================================================================

if __name__ == "__main__":
    test_prompt = "What is love and how does it affect human behavior?"
    final = swarm_middle_agent_interface(test_prompt)
    if final:
        print(Fore.CYAN + f"\nSwarm final answer:\n{final}\n" + Style.RESET_ALL)
    else:
        print(Fore.CYAN + "No final swarm response captured." + Style.RESET_ALL)
Download .txt
gitextract_mwuws8yk/

├── .gitignore
├── LICENSE
├── README.md
├── agents.json
├── reasoning.py
├── requirements.txt
└── swarm_middle_agent.py
Download .txt
SYMBOL INDEX (43 symbols across 2 files)

FILE: reasoning.py
  class ColoredFormatter (line 25) | class ColoredFormatter(logging.Formatter):
    method format (line 42) | def format(self, record):
  function append_session_record (line 105) | def append_session_record(file_path: str, record: dict):
  function append_reasoning_history (line 130) | def append_reasoning_history(record: dict):
  function append_swarm_history (line 134) | def append_swarm_history(record: dict):
  function load_reasoning_history_for_context (line 138) | def load_reasoning_history_for_context(max_records=5, search_keywords=No...
  function load_swarm_history_for_context (line 181) | def load_swarm_history_for_context(max_records=5, search_keywords=None):
  function get_local_context_for_prompt (line 219) | def get_local_context_for_prompt(user_prompt, is_swarm=False, max_record...
  function load_agents_config (line 259) | def load_agents_config():
  function get_shared_system_message (line 280) | def get_shared_system_message():
  class Agent (line 321) | class Agent:
    method __init__ (line 332) | def __init__(self, color, **kwargs):
    method _add_message (line 352) | def _add_message(self, role, content, mode='reasoning'):
    method _handle_reasoning_logic (line 380) | def _handle_reasoning_logic(self, prompt):
    method _handle_chat_interaction (line 451) | def _handle_chat_interaction(self, user_message):
    method discuss (line 525) | def discuss(self, prompt):
    method verify (line 537) | def verify(self, data):
    method refine (line 550) | def refine(self, data, more_time=False, iterations=2):
    method critique (line 576) | def critique(self, other_agent_response):
    method ask_other_agent (line 593) | def ask_other_agent(self, other_agent, question):
  function initialize_agents (line 612) | def initialize_agents():
  function blend_responses (line 687) | def blend_responses(agent_responses, user_prompt):
  function print_divider (line 746) | def print_divider(char="═", length=100, color=Fore.YELLOW):
  function print_header (line 752) | def print_header(title, color=Fore.YELLOW):
  function process_agent_action (line 761) | def process_agent_action(agent, action, *args, **kwargs):
  function handle_special_commands (line 795) | def handle_special_commands(user_input, agents):
  function chat_with_agents (line 829) | def chat_with_agents(agents):
  function reasoning_logic (line 885) | def reasoning_logic(agents):
  function save_reasoning_session (line 1082) | def save_reasoning_session(user_prompt, final_response, user_feedback, c...
  function swarm_reasoning_feedback (line 1106) | def swarm_reasoning_feedback(user_prompt, final_response):
  function save_swarm_session (line 1148) | def save_swarm_session(user_prompt, final_response, user_feedback, conte...
  function main_menu (line 1172) | def main_menu():

FILE: swarm_middle_agent.py
  class ColoredFormatter (line 16) | class ColoredFormatter(logging.Formatter):
    method format (line 32) | def format(self, record):
  function initialize_swarm_client (line 68) | def initialize_swarm_client():
  function load_agents_config (line 91) | def load_agents_config():
  function print_divider (line 114) | def print_divider(char="═", length=100, color=Fore.YELLOW):
  function print_header (line 125) | def print_header(title, color=Fore.YELLOW):
  function initialize_swarm_agents (line 142) | def initialize_swarm_agents():
  function run_swarm_reasoning (line 218) | def run_swarm_reasoning(user_prompt):
  function blend_responses (line 309) | def blend_responses(agent_responses, user_prompt):
  function get_agent_color (line 375) | def get_agent_color(agent_name):
  function swarm_middle_agent_interface (line 396) | def swarm_middle_agent_interface(user_prompt):
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (132K chars).
[
  {
    "path": ".gitignore",
    "chars": 3220,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2024 Madie Laine\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "README.md",
    "chars": 36168,
    "preview": "# Multi-Agent Reasoning with Memory and Swarm Framework\n\n![Multi-Agent Reasoning Banner](img/reasoningbanner.png)\n\n## Ta"
  },
  {
    "path": "agents.json",
    "chars": 16381,
    "preview": "{\n  \"agents\": [\n    {\n      \"name\": \"Agent 47\",\n      \"system_purpose\": \"Your primary role is to assist the user by prov"
  },
  {
    "path": "reasoning.py",
    "chars": 53144,
    "preview": "import os\nimport sys\nimport time\nimport logging\nimport json\nimport re\nfrom concurrent.futures import ThreadPoolExecutor\n"
  },
  {
    "path": "requirements.txt",
    "chars": 74,
    "preview": "openai\ncolorama\ntiktoken\ngit+https://github.com/openai/swarm.git#egg=swarm"
  },
  {
    "path": "swarm_middle_agent.py",
    "chars": 16013,
    "preview": "import os\nimport sys\nimport time\nimport logging\nimport json\nfrom colorama import Fore, Style, init\nfrom swarm import Age"
  }
]

About this extraction

This page contains the full source code of the AdieLaine/multi-agent-reasoning GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (123.1 KB), approximately 26.1k tokens, and a symbol index with 43 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!