Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/react v3 #163

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/images/react.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
145 changes: 145 additions & 0 deletions examples/react/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# ReAct Example

ReAct (Reasoning and Acting) is a paradigm that combines reasoning and acting in an interleaved manner. The agent uses reasoning to determine what actions to take and interprets the results of those actions to inform further reasoning.

This example demonstrates how to use the framework for ReAct tasks. The example code can be found in the `examples/react_example` directory.

```bash
cd examples/react_example
```

## Overview

This example implements a ReAct workflow that consists of following components:

1. **Input Interface**
- Handles user input containing questions
- Supports both CLI and programmatic interfaces

2. **ReAct Workflow**
- Think: Reason about the current state and decide next action
- Act: Execute the chosen action (e.g., web search)
- Observe: Process the results of the action
- Repeat until the task is complete or max steps reached

### The workflow follows this pattern:

<img src="../../docs/images/react.png" width="200" alt="ReAct Workflow">

1. User provides input (question)
2. Agent thinks about the question and decides to take an action (e.g., search)
3. Agent observes the result
4. Process repeats until the answer is found

## Prerequisites

- Python 3.10+
- Required packages installed (see requirements.txt)
- Access to OpenAI API or compatible endpoint (see configs/llms/*.yml)
- Redis server running locally or remotely
- Conductor server running locally or remotely

## Configuration

The container.yaml file manages dependencies and settings for different components of the system. To set up your configuration:

1. Configure your container.yaml:
- Set Redis connection settings for both `redis_stream_client` and `redis_stm_client`
- Update the Conductor server URL under conductor_config section
- Adjust any other component settings as needed

2. Configure your LLM settings in configs:
```bash
export custom_openai_key="your_openai_api_key"
export custom_openai_endpoint="your_openai_endpoint"
```

## Running the Example

You can run the example in three ways:

1. Using the CLI interface:
```bash
python run_cli.py
```

2. Using the programmatic interface:
```bash
python run_programmatic.py
```

3. Running batch testing with dataset:
```bash
python run_batch_test.py [options]
```
This script will:
- Read questions from the input dataset
- Process each question using the ReAct workflow
- Save the results in a standardized format
- Output results to `data/{dataset_name}_{alg}_{model_id}_results.json`

Available options:
```bash
--input_file Input dataset file path (relative to project root)
Default: data/hotpot_dev_select_500_data_test_0107.jsonl

--dataset_name Name of the dataset
Default: hotpot

--model_id Model identifier
Default: gpt-3.5-turbo

--alg Algorithm name
Default: ReAct

--output_dir Output directory for results (relative to project root)
Default: data
```

Example usage:
```bash
python run_batch_test.py \
--input_file data/custom_test.jsonl \
--dataset_name custom \
--model_id gpt-4 \
--alg ReAct-v2 \
--output_dir results
```

When running the CLI or programmatic interface, you'll be prompted to:
1. Input an example (optional, press Enter to skip)
2. Set maximum turns (optional, default is 10)
3. Input your question

## Troubleshooting

If you encounter issues:
- Verify Redis is running and accessible
- Check your OpenAI API key is valid
- Ensure all dependencies are installed correctly
- Review logs for any error messages
- Check the proxy settings if needed

Common issues:
- Connection errors: Check your network settings
- LLM errors: Verify your API key and endpoint
- Redis errors: Ensure Redis server is running

## Example Usage

Here's a simple example of using the ReAct agent:

```bash
$ python run_cli.py

Please input your question:
The time when the first computer was invented?

[Agent starts reasoning and searching...]
```

The agent will then:
1. Think about how to answer the question
2. Search for relevant information
3. Process the search results
4. Provide a final answer based on the found information
1 change: 1 addition & 0 deletions examples/react/agent/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

34 changes: 34 additions & 0 deletions examples/react/agent/input_interface/input_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from pathlib import Path
from omagent_core.utils.registry import registry
from omagent_core.engine.worker.base import BaseWorker
from omagent_core.utils.logger import logging
import uuid

CURRENT_PATH = Path(__file__).parents[0]

@registry.register_worker()
class InputInterface(BaseWorker):
def _process_input(self, input_data):
"""Process input data and extract text content"""
if not input_data or 'messages' not in input_data:
return None

message = input_data['messages'][-1]
for content in message.get('content', []):
if content.get('type') == 'text':
return content.get('data', '').strip()
return None

def _run(self, *args, **kwargs):
# Get main question input
user_input = self.input.read_input(
workflow_instance_id=self.workflow_instance_id,
input_prompt='Please input your question:'
)
query = self._process_input(user_input)

# Return parameters
return {
'query': query, # User's question
'id': str(uuid.uuid4()) # Generate unique ID
}
15 changes: 15 additions & 0 deletions examples/react/compile_container.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from pathlib import Path

from omagent_core.utils.container import container
from omagent_core.utils.registry import registry

# Set up path and import modules
CURRENT_PATH = root_path = Path(__file__).parents[0]
registry.import_module()

# Register required components
container.register_callback(callback="AppCallback")
container.register_input(input="AppInput")
container.register_stm("RedisSTM")
# Compile container config
container.compile_config(CURRENT_PATH)
8 changes: 8 additions & 0 deletions examples/react/configs/llms/text_res.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: OpenaiGPTLLM
model_id: gpt-3.5-turbo
api_key: ${env| custom_openai_key, openai_api_key}
endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
temperature: 0
stream: false
response_format: text
use_default_sys_prompt: false
1 change: 1 addition & 0 deletions examples/react/configs/workers/input_interface.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: InputInterface
20 changes: 20 additions & 0 deletions examples/react/configs/workers/react_workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
- name: ThinkAction
llm: ${sub|text_res}
output_parser:
name: StrParser
max_steps: 8
example: |
Question: Musician and satirist Allie Goertz wrote a song about the "The Simpsons" character Milhouse, who Matt Groening named after who?
Thought 1: The question simplifies to "The Simpsons" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.
Action 1: Search[Milhouse]
Observation 1: Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.
Thought 2: The paragraph does not tell who Milhouse is named after, maybe I can look up "named after".
Action 2: Lookup[named after]
Observation 2: (Result 1 / 1) Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.
Thought 3: Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.
Action 3: Finish[Richard Nixon]


- name: WikiSearch

- name: ReactOutput
142 changes: 142 additions & 0 deletions examples/react/run_batch_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from omagent_core.utils.container import container
from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
from pathlib import Path
from omagent_core.utils.registry import registry
from omagent_core.clients.devices.programmatic.client import ProgrammaticClient
from omagent_core.utils.logger import logging
from omagent_core.advanced_components.workflow.react.workflow import ReactWorkflow
import json
import argparse


def read_input_texts(file_path):
"""Read questions from jsonl file"""
input_texts = []
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
if line.strip():
data = json.loads(line)
input_texts.append((data['question'], str(data['id'])))
return input_texts


def process_results(results, dataset_name="aqua", model_id="gpt-3.5-turbo", alg="ReAct"):
"""Convert results to standard format"""
formatted_output = {
"dataset": dataset_name,
"model_id": model_id,
"alg": alg,
"model_result": []
}

for result in results:
output_data = result.get('output', {})

model_result = {
"id": output_data.get('id'),
"question": output_data.get('query'),
"body": output_data.get('body', {}),
"last_output": output_data.get('output', ''),
"step_number": output_data.get('step_number', 0),
"prompt_tokens": output_data.get('token_usage', {}).get('prompt_tokens', 0),
"completion_tokens": output_data.get('token_usage', {}).get('completion_tokens', 0)
}

formatted_output["model_result"].append(model_result)

return formatted_output


def run_workflow(args):
"""Run the ReAct workflow with given arguments"""
logging.init_logger("omagent", "omagent", level="INFO")

# Set current working directory path
CURRENT_PATH = Path(__file__).parents[0]
ROOT_PATH = CURRENT_PATH.parents[1] # 项目根目录

# Import registered modules
registry.import_module(CURRENT_PATH.joinpath('agent'))

# Load container configuration from YAML file
container.register_stm("RedisSTM")
container.from_config(CURRENT_PATH.joinpath('container.yaml'))

# Initialize workflow
workflow = ConductorWorkflow(name='react_basic_workflow_example')

# Configure React Basic workflow
react_workflow = ReactWorkflow()
react_workflow.set_input(
query=workflow.input('query'),
id=workflow.input('id')
)

# Configure workflow execution flow
workflow >> react_workflow

# Register workflow
workflow.register(overwrite=True)

# Read input data
input_file = ROOT_PATH / args.input_file
input_data = read_input_texts(input_file)

# Initialize programmatic client
config_path = CURRENT_PATH.joinpath('configs')
programmatic_client = ProgrammaticClient(
processor=workflow,
config_path=config_path,
workers=[] # React workflow 不需要额外的 workers
)

# Prepare input data
workflow_input_list = [
{"query": item[0], "id": str(item[1])} for item in input_data
]

print(f"Processing {len(workflow_input_list)} queries in this split...")

# Process data
res = programmatic_client.start_batch_processor(
workflow_input_list=workflow_input_list
)

# Process results
formatted_results = process_results(
res,
dataset_name=args.dataset_name,
model_id=args.model_id,
alg=args.alg
)

# Create output directory if it doesn't exist
output_dir = ROOT_PATH / args.output_dir
output_dir.mkdir(parents=True, exist_ok=True)

# Save results to file
output_file = output_dir / f"{args.dataset_name}_{args.alg}_{args.model_id}_results.json"
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(formatted_results, f, ensure_ascii=False, indent=2)

programmatic_client.stop_processor()
print(f"Results saved to {output_file}")
print("All splits processed successfully!")


if __name__ == "__main__":
# Parse command line arguments
parser = argparse.ArgumentParser(description='Run ReAct workflow on dataset')
parser.add_argument('--input_file', type=str, default="data/hotpot_dev_select_500_data_test_0107.jsonl",
help='Input dataset file path (relative to project root)')
parser.add_argument('--dataset_name', type=str, default="hotpot",
help='Name of the dataset')
parser.add_argument('--model_id', type=str, default="gpt-3.5-turbo",
help='Model identifier')
parser.add_argument('--alg', type=str, default="ReAct",
help='Algorithm name')
parser.add_argument('--output_dir', type=str, default="data",
help='Output directory for results (relative to project root)')

args = parser.parse_args()
run_workflow(args)
Loading