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

Add config and react agent #6

Merged
merged 7 commits into from
Jun 15, 2023
Merged
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
103 changes: 82 additions & 21 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,86 @@
name: example
name: main
version: 0.0.1
description: example
type: rewoo
prompt_template:
- Planner:
input_variables:
- foo
template: "Say {foo}"
- Solver:
input_variables:
- foo
template: "Say {foo}"
llm:
- Planner:
name: llm
description: 111
model_param:
model_name: 123
model_name: 111123
- Solver:
name: llm
description: 122223
model_param:
model_name: 123
model_name: 123
target_tasks:
- print
- find

plugins:
- name: main
version: 0.0.1
description: example
type: rewoo
prompt_template:
- Planner:
input_variables:
- foo
template: "Say {foo}"
- Solver:
input_variables:
- foo
template: "Say {foo}"
llm:
- Planner:
name: llm
description: 111
model_param:
model_name: 123
model_name: 111123
- Solver:
name: llm
description: 122223
model_param:
model_name: 123
model_name: 123

## - !include: ./plugins/React/React-Router.js
## - !include: ./plugins/React/React-Redux.js
# - name: React-Router
# type: rewoo
# version: 0.0.1
# description: example
# prompt_template:
# input_variables:
# - foo
# template: "Say {foo}"
# llm:
## - Planner:
# name: llm
# description: 111
# model_param:
# model_name: 123
# model_name: 111123
## - Solver:
## name: llm
## description: 122223
## model_param:
## model_name: 123
## model_name: 123
## plugins:
## - !include: ./plugins/React/React-Router.js


agents:
- solver: !include solver.yaml
- planner:
name: GPT4
version: 0.0.1
description: example
config:
temperature: 0.1
api_key: example
token_limit: 1000
prompt: example
- worker:
- name: Google
description: Worker that searches results from Google.
config:
max_length: 10
- name: WolfAlpha
description: Worker that searches results from WolfAlpha.
config:
api_key: example

3 changes: 0 additions & 3 deletions gentopia/agent/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
from gentopia.agent.tools.google_search import GoogleSearch
from gentopia.agent.tools.wikipedia import Wikipedia

WORKER_REGISTRY = {"Google": GoogleSearch, "Wikipedia": Wikipedia}
8 changes: 4 additions & 4 deletions gentopia/agent/base_agent.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from abc import ABC, abstractmethod
from typing import List, Dict, Union, Any
from typing import List, Dict, Union, Any, Optional, Type

from langchain import PromptTemplate
from pydantic import BaseModel
from pydantic import BaseModel, create_model

from gentopia.llm.base_llm import BaseLLM
from gentopia.model.agent_model import AgentType, AgentOutput
Expand All @@ -17,8 +17,8 @@ class BaseAgent(ABC, BaseModel):
llm: Union[BaseLLM, Dict[str, BaseLLM]]
prompt_template: Union[PromptTemplate, Dict[str, PromptTemplate]]
plugins: List[Any]

args_schema: Optional[Type[BaseModel]] = create_model("ArgsSchema", input=(str, ...))

@abstractmethod
def run(self, input) -> AgentOutput:
def run(self, *args, **kwargs) -> AgentOutput:
pass
27 changes: 27 additions & 0 deletions gentopia/agent/plugin_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pathlib import Path

from gentopia.config.agent_config import AgentConfig


class PluginManager:
def __init__(self, config):
if isinstance(config, str) or isinstance(config, Path):
config = AgentConfig(file=config)
elif isinstance(config, list):
config = AgentConfig(config=config)
config.get_agent()
self.config = config
self.plugins = self.config.plugins

def run(self, name, *args, **kwargs):
if name not in self.plugins:
return "No evidence found"
plugin = self.plugins[name]
return plugin.run(*args, **kwargs)

def __call__(self, plugin, *args, **kwargs):
self.run(plugin, *args, **kwargs)

# @property
# def cost(self):
# return {name: self.plugins[name].cost for name in self.tools}
1 change: 1 addition & 0 deletions gentopia/agent/react/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .agent import ReactAgent
129 changes: 127 additions & 2 deletions gentopia/agent/react/agent.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,132 @@
import logging
import re
from typing import List, Union, Optional, Type, Tuple

from langchain import PromptTemplate
from langchain.schema import AgentFinish
from langchain.tools import BaseTool
from pydantic import create_model, BaseModel

from gentopia.agent.base_agent import BaseAgent
from gentopia.config.task import AgentAction
from gentopia.llm.client.openai import OpenAIGPTClient
from gentopia.model.agent_model import AgentType, AgentOutput
from gentopia.util.cost_helpers import calculate_cost

FINAL_ANSWER_ACTION = "Final Answer:"


# TODO: Implement this class
class ReactAgent(BaseAgent):
name: str = "ReactAgent"
type: AgentType = AgentType.REACT
version: str
description: str
target_tasks: list[str]
llm: OpenAIGPTClient
prompt_template: PromptTemplate
plugins: List[Union[BaseTool, BaseAgent]]
examples: Union[str, List[str]] = None
args_schema: Optional[Type[BaseModel]] = create_model("ReactArgsSchema", instruction=(str, ...))

intermediate_steps: List[Tuple[AgentAction, str]] = []

def _compose_plugin_description(self) -> str:
"""
Compose the worker prompt from the workers.

Example:
toolname1[input]: tool1 description
toolname2[input]: tool2 description
"""
prompt = ""
try:
for plugin in self.plugins:
prompt += f"{plugin.name}[input]: {plugin.description}\n"
except Exception:
raise ValueError("Worker must have a name and description.")
return prompt

def _construct_scratchpad(
self, intermediate_steps: List[Tuple[AgentAction, str]]
) -> str:
"""Construct the scratchpad that lets the agent continue its thought process."""
thoughts = ""
for action, observation in intermediate_steps:
thoughts += action.log
thoughts += f"\nObservation: {observation}\nThought:"
return thoughts

def _parse_output(self, text: str) -> Union[AgentAction, AgentFinish]:
includes_answer = FINAL_ANSWER_ACTION in text
regex = (
r"Action\s*\d*\s*:[\s]*(.*?)[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
)
action_match = re.search(regex, text, re.DOTALL)
if action_match:
if includes_answer:
raise Exception(
"Parsing LLM output produced both a final answer "
f"and a parse-able action: {text}"
)
action = action_match.group(1).strip()
action_input = action_match.group(2)
tool_input = action_input.strip(" ")
# ensure if its a well formed SQL query we don't remove any trailing " chars
if tool_input.startswith("SELECT ") is False:
tool_input = tool_input.strip('"')

return AgentAction(action, tool_input, text)

elif includes_answer:
return AgentFinish(
{"output": text.split(FINAL_ANSWER_ACTION)[-1].strip()}, text
)

if not re.search(r"Action\s*\d*\s*:[\s]*(.*?)", text, re.DOTALL):
raise Exception(
f"Could not parse LLM output: `{text}`",
)
elif not re.search(
r"[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)", text, re.DOTALL
):
raise Exception(
f"Could not parse LLM output: `{text}`"
)
else:
raise Exception(f"Could not parse LLM output: `{text}`")

def _compose_prompt(self, instruction) -> str:
"""
Compose the prompt from template, worker description, examples and instruction.
"""
agent_scratchpad = self._construct_scratchpad(self.intermediate_steps)
tool_description = self._compose_plugin_description()
tool_names = ", ".join([plugin.name for plugin in self.plugins])
if self.prompt_template is None:
from gentopia.prompt.react import ZeroShotReactPrompt
self.prompt_template = ZeroShotReactPrompt
return self.prompt_template.format(
instruction=instruction,
agent_scratchpad=agent_scratchpad,
tool_description=tool_description,
tool_names=tool_names
)

def run(self, instruction):
pass
logging.info(f"Running {self.name + ':' + self.version} with instruction: {instruction}")
total_cost = 0.0
total_token = 0

prompt = self._compose_prompt(instruction)
logging.info(f"Prompt: {prompt}")
response = self.llm.completion(prompt)
if response.state == "error":
logging.error("Planner failed to retrieve response from LLM")
raise ValueError("Planner failed to retrieve response from LLM")

logging.info(f"Planner run successful.")
total_cost += calculate_cost(self.llm.model_name, response.prompt_token,
response.completion_token)
total_token += response.prompt_token + response.completion_token
self.intermediate_steps.append(self._parse_output(response.content))
return AgentOutput(output=response.content, cost=total_cost, token_usage=total_token)
1 change: 1 addition & 0 deletions gentopia/agent/rewoo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .agent import RewooAgent
5 changes: 2 additions & 3 deletions gentopia/agent/rewoo/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class RewooAgent(BaseAgent):
prompt_template: Dict[str, PromptTemplate] # {"Planner": xxx, "Solver": xxx}
plugins: List[Union[BaseTool, BaseAgent]]
examples: Dict[str, Union[str, List[str]]] = None
logger = logging.getLogger('application')
# logger = logging.getLogger('application')

def _get_llms(self):
if isinstance(self.llm, BaseLLM):
Expand Down Expand Up @@ -72,9 +72,8 @@ def _parse_planner_evidences(self, planner_response: str) -> (dict[str, str], Li
Example:
{"*E1": "Tool1", "*E2": "Tool2", "*E3": "Tool3", "*E4": "Tool4"}, [[*E1, *E2], [*E3, *E4]]
"""
evidences = dict()
evidences, dependence = dict(), dict()
num = 0
dependence = dict()
for line in planner_response.splitlines():
if line.startswith("*E") and line[2].isdigit():
e, tool_call = line.split(":", 1)
Expand Down
4 changes: 1 addition & 3 deletions gentopia/agent/rewoo/nodes/Planner.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import logging
from typing import List, Union

from langchain.tools import BaseTool
from pydantic import BaseModel

from gentopia.agent.base_agent import BaseAgent
from gentopia.llm.base_llm import BaseLLM
from gentopia.llm.llm_info import *
from gentopia.model.completion_model import BaseCompletion
from gentopia.prompt.rewoo import *

Expand All @@ -16,7 +14,7 @@ class Planner(BaseModel):
prompt_template: PromptTemplate = None
examples: Union[str, List[str]] = None
workers: List[Union[BaseTool, BaseAgent]]
logger = logging.getLogger('application')
# logger = logging.getLogger('application')

def _compose_worker_description(self) -> str:
"""
Expand Down
4 changes: 1 addition & 3 deletions gentopia/agent/rewoo/nodes/Solver.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import logging
from typing import List, Union

from pydantic import BaseModel

from gentopia.llm.base_llm import BaseLLM
from gentopia.llm.llm_info import *
from gentopia.model.completion_model import BaseCompletion
from gentopia.prompt.rewoo import *

Expand All @@ -13,7 +11,7 @@ class Solver(BaseModel):
model: BaseLLM
prompt_template: PromptTemplate = None
examples: Union[str, List[str]] = None
logger = logging.getLogger('application')
# logger = logging.getLogger('application')

def _compose_fewshot_prompt(self) -> str:
if self.examples is None:
Expand Down
Loading