This commit is contained in:
CakeCN
2024-12-03 16:21:19 +08:00
commit 4198ca63b1
975 changed files with 333413 additions and 0 deletions

View File

@@ -0,0 +1,476 @@
# -*- coding: utf-8 -*-
"""An agent class that implements the ReAct algorithm. The agent will reason
and act iteratively to solve problems. More details can be found in the paper
https://arxiv.org/abs/2210.03629.
"""
from typing import Any, Callable, Optional, Union, Sequence
from loguru import logger
from agent.flex_service_toolkit import FlexServiceToolkit
from agentscope.exception import ResponseParsingError, FunctionCallError
from agentscope.agents import AgentBase
from agentscope.memory.temporary_memory import TemporaryMemory
from agentscope.message import Msg
from agentscope.parsers import MarkdownJsonDictParser
from agentscope.service.service_toolkit import ServiceFunction
from agentscope.service import (
ServiceToolkit,
ServiceResponse,
ServiceExecStatus,
)
import json
# 这个INSTRUCTION_PROMPT 交给免费Agent进行使用不包含评测与得分标准但是会在没有自己注释的大段代码或复制粘贴代码是通知学生进入一次理解性测试。
INSTRUCTION_PROMPT = """## Your Role:
You are a powerful AI agent to teach student computer algorithm, you are to company a student to learn algorithm or solve problems.
## What You Should Do:
0. You MUST follow the teaching instructions made by human teacher with several chapters, ONLY focus on one chapter at a time.
1. Student may ask you some questions, DONT answer the question directly or completely, instead, based on context (like student's code) to answer only one step forward.
2. Each chapter in teaching instruction will specifically guide what to teach to student, follow the instruction.
3. System will gather student's infomation sometimes, you neet to analyze student's learning statement from student's history codes, call the necessary function we he is stuck.
## Note:
1. Fully understand the tool functions and their arguments before using them.
2. Make sure the types and values of the arguments you provided to the tool functions are correct.
3. Don't take things for granted. For example, where you are, what's the time now, etc. You can try to use the tool functions to get information.
4. If the function execution fails, you should analyze the error and try to solve it.
5. Remember to decide whether the student's question or idea is regarding about current chapter, if not, you should not awnser and ask the student to focus on the chapter.
## Resources:
1. The tool functions you can use.
2. The context, including the student's code, and teaching instructions.
""" # noqa
CHAPTER_FINISH = 0
CHAPTER_FOCUS = 1
CHAPTER_LATTER = 2
CHAPTER_FAILED = 3
class Chapter():
def __init__(self, No = 1, focus = 2,title="", chapter = "", chapter_prompt = "", score_prompt = "", append_tools : Callable[..., Any] = None) -> None:
self.No = No
self.focus = focus
self.chapter = chapter
self.append_tools = append_tools
self.memory = None
def GetChapter(self):
if self.focus == CHAPTER_FINISH: f = "(Finished Chapter)"
elif self.focus == CHAPTER_FOCUS: f = "(Your Now Chapter)"
elif self.focus == CHAPTER_LATTER: f = "(Ignore for Latter Chapter)"
elif self.focus == CHAPTER_FAILED: f = "(Failed Chapter)"
return f"{f} Chapter {self.No}: {self.chapter}"
def Finished(self, successful: bool = True):
if successful: self.focus = 0
else: self.focus = 3
def Focus(self):
self.focus = 1
def record(content: str, name: str):
"""Call it to save some important info for other chapters. Like file path, function call example.
Args:
content (`str`): The content to be recorded.
name (`str`): The name of the agent to record.
Returns:
None
"""
ChapterChainAgent.Instance[name].memory.add(Msg(
"assistant", content, role="assistant"
))
ChapterChainAgent.Instance[name].chapter_memory.add(Msg(
"assistant", "I recorded globally: "+content, role="assistant"
))
status = ServiceExecStatus.SUCCESS
return ServiceResponse(status, None)
def next_chapter(name: str):
"""Call it to go to next chapter.
Args:
name (`str`): The name of the agent to go to next chapter.
Returns:
None
"""
try:
ChapterChainAgent.Instance[name].next_chapter()
except:
status = ServiceExecStatus.ERROR
return ServiceResponse(status, "Failed to go to next chapter. name '"+name+"' not found in ChapterChainAgent.Instance")
status = ServiceExecStatus.SUCCESS
return ServiceResponse(status, None)
class ChapterChainAgent(AgentBase):
"""An agent class to preform chapter chain algorithm.
Project will be split to some chapters chained.
Each chapter will haave a main objective, having its own tools, and holding its own context.
"""
Instance = {}
def __init__(
self,
name: str,
model_config_name: str,
service_toolkit: FlexServiceToolkit = None,
sys_prompt: str = "You're a helpful assistant.",
max_iters: int = 10,
verbose: bool = True,
chapter_chain: Sequence[Chapter] = None,
stop_when_one_CHAPTER_failed: bool = True,
**kwargs: Any,
) -> None:
"""
Args:
name (`str`):
The name of the agent.
sys_prompt (`str`):
The system prompt of the agent.
model_config_name (`str`):
The name of the model config, which is used to load model from
configuration.
service_toolkit (`ServiceToolkit`):
A `ServiceToolkit` object that contains the tool functions.
max_iters (`int`, defaults to `10`):
The maximum number of iterations of each chapter.
verbose (`bool`, defaults to `True`):
Whether to print the detailed information during reasoning and
acting steps. If `False`, only the content in speak field will
be print out.
chapter_chain (`Sequence[str]`):
The chapter chain to be performed. If None, the agent will add a
init chapter to construct the chapter chain.
"""
super().__init__(
name=name,
sys_prompt=sys_prompt,
model_config_name=model_config_name,
)
# TODO: To compatible with the old version, which will be deprecated
# soon
if "tools" in kwargs:
logger.warning(
"The argument `tools` will be deprecated soon. "
"Please use `service_toolkit` instead. Example refers to "
"https://github.com/modelscope/agentscope/blob/main/"
"examples/conversation_with_react_agent/code/"
"conversation_with_react_agent.py",
)
service_funcs = {}
for func, json_schema in kwargs["tools"]:
name = json_schema["function"]["name"]
service_funcs[name] = ServiceFunction(
name=name,
original_func=func,
processed_func=func,
json_schema=json_schema,
)
if service_toolkit is None:
service_toolkit = FlexServiceToolkit()
service_toolkit.service_funcs = service_funcs
else:
service_toolkit.service_funcs.update(service_funcs)
elif service_toolkit is None:
raise ValueError(
"The argument `service_toolkit` is required to initialize "
"the ReActAgent.",
)
service_toolkit.add(record,name=name)
service_toolkit.add(next_chapter,name=name)
self.service_toolkit = service_toolkit
self.service_toolkit.agent = self
self.verbose = verbose
self.max_iters = max_iters
if not sys_prompt.endswith("\n"):
sys_prompt = sys_prompt + "\n"
self.sys_prompt = "\n".join(
[
# The brief intro of the role and target
sys_prompt.format(name=self.name),
# The detailed instruction prompt for the agent
INSTRUCTION_PROMPT,
],
)
# This memory is used to store all useful memory with the chapter chain switching
#self.memory_on_chain = TemporaryMemory()
self.memory.add(Msg("system", self.sys_prompt, role="system"))
self.chapter_memory = TemporaryMemory()
# Initialize a parser object to formulate the response from the model
self.parser = MarkdownJsonDictParser(
content_hint={
"thought": "what you thought",
"speak": "what you speak",
"function": service_toolkit.tools_calling_format,
},
required_keys=["thought", "speak", "function"],
# Only print the speak field when verbose is False
keys_to_content=True if self.verbose else "speak",
)
self.chapter_chain = chapter_chain
self.chapter_chain_now = 0
self.finish_now_chapter = False
self.stop_when_one_CHAPTER_failed = stop_when_one_CHAPTER_failed
ChapterChainAgent.Instance[name] = self
def chapter_chain_to_memory(self):
mem = "##The Chapter Chain##\n"
for chapter in self.chapter_chain:
mem+=chapter.GetChapter()+"\n"
mem+="\n"
mem+= "Remember your now chapter is "+ self.chapter_chain[self.chapter_chain_now].GetChapter()+"\n"
return Msg("system", mem, role="system")
def _has_unfinished_chapter(self):
for chapter in self.chapter_chain:
if chapter.focus == CHAPTER_LATTER or chapter.focus == CHAPTER_FOCUS:
return True
return False
def next_chapter(self, successful: bool = True):
if self.chapter_chain[self.chapter_chain_now].append_tools is not None:
for callable_tools in self.chapter_chain[self.chapter_chain_now].append_tools:
self.service_toolkit.Remove(callable_tools)
self.chapter_chain[self.chapter_chain_now].Finished(successful)
for i in range(len(self.chapter_chain)):
if self.chapter_chain[i].focus == 2:
self.chapter_chain[i].Focus()
if self.chapter_chain[i].append_tools is not None:
for callable_tools in self.chapter_chain[i].append_tools:
self.service_toolkit.add(callable_tools)
self.chapter_chain_now = i
return
def add_chapters_after(self, CHAPTER_No, chapters: Sequence[Chapter]):
for i in range(len(self.chapter_chain)):
if self.chapter_chain[i].No == CHAPTER_No:
for j in range(len(chapters)):
CHAPTER_to_insert = Chapter(No = CHAPTER_No + j + 1, chapter = chapters[j], focus = 2, append_tools=
self.chapter_chain[self.chapter_chain_now].append_tools)
self.chapter_chain.insert(i+1+j, CHAPTER_to_insert)
for j in range(i+len(chapters), len(self.chapter_chain)):
self.chapter_chain[j].No += len(chapters)
break
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None, user_backboard:str = "") -> Msg:
if self.chapter_memory.size() == 0:
self.chapter_chain[self.chapter_chain_now].memory = self.chapter_memory
for msg in self.memory.get_memory():
self.chapter_memory.add(msg)
self.chapter_memory.add(self.chapter_chain_to_memory())
self.chapter_memory.add(x)
for _ in range(1):
# Step 1: Thought
if self.verbose:
self.speak(f" ITER {_+1}, STEP 1: REASONING ".center(70, "#"))
# Prepare hint to remind model what the response format is
# Won't be recorded in memory to save tokens
backboard_hint_msg = Msg(
"system",
user_backboard,
role="system",
echo=self.verbose,
)
tools_hint_msg = Msg(
"system",
self.service_toolkit.tools_instruction,
role="system",
echo=self.verbose,
)
hint_msg = Msg(
"system",
self.parser.format_instruction+"\n ####",
role="system",
echo=self.verbose,
)
prompt = self.model.format(self.chapter_memory.get_memory(),
backboard_hint_msg,
# The instruction prompt for tools
tools_hint_msg,
hint_msg)
print("*********************************************************************")
print(prompt)
if self.verbose:
self.speak(f"Prompt".center(70, "#"))
self.speak(str(self.chapter_memory.get_memory()[-1].content))
# print(prompt)
try:
res = self.model(
prompt,
parse_func=self.parser.parse,
max_retries=1,
)
if self.verbose:
self.speak(f"Result Parsed".center(70, "#"))
self.speak(str(res.parsed))
print(res.parsed)
self.chapter_memory.add(
Msg(
self.name,
self.parser.to_memory(res.parsed),
"assistant",
),
)
# Print out the response
msg_returned = Msg(
self.name,
self.parser.to_content(res.parsed),
"assistant",
)
if self.verbose:
self.speak(f"Speak".center(70, "#"))
print(msg_returned)
self.speak(msg_returned)
return msg_returned
if self.verbose:
self.speak(f"Function".center(70, "#"))
self.speak(str(res.parsed["function"]))
print(res.parsed["function"])
arg_function = res.parsed["function"]
if (
isinstance(arg_function, str)
and arg_function in ["[]", ""]
or isinstance(arg_function, list)
and len(arg_function) == 0
):
# Only the speak field is exposed to users or other agents
continue
# Only catch the response parsing error and expose runtime
# errors to developers for debugging
except ResponseParsingError as e:
# Print out raw response from models for developers to debug
response_msg = Msg(self.name, e.raw_response, "assistant")
self.speak(response_msg)
# Re-correct by model itself
error_msg = Msg("system", str(e), "system")
self.speak(error_msg)
self.chapter_memory.add([response_msg, error_msg])
# Skip acting step to re-correct the response
continue
# 2. Acting
if self.verbose:
self.speak(f" ITER {_+1}, STEP 2: ACTING ".center(70, "#"))
# Parse, check and execute the tool functions in service toolkit
try:
execute_results = self.service_toolkit.parse_and_call_func(
res.parsed["function"],
)
# 在这里补充任务结束行动
if self.finish_now_chapter:
self.finish_now_chapter = False
hint_msg = Msg(
"system",
"You have finished your own chapter."
"Now generate a reply by summarizing the current "
"situation. Remember this summary will be recorded in global memory. Make sure keep important information.",
role="system",
echo=self.verbose,
)
# Generate a reply by summarizing the current situation
prompt = self.model.format(self.chapter_memory.get_memory(), hint_msg)
res = self.model(prompt)
res_msg = Msg(self.name, res.text, "assistant")
self.memory.add(res_msg)
self.speak(res_msg)
self.next_chapter(successful=True)
self.success_own_chapter = True
break
# Note: Observing the execution results and generate response
# are finished in the next reasoning step. We just put the
# execution results into memory, and wait for the next loop
# to generate response.
# Record execution results into memory as system message
msg_res = Msg("system", execute_results, "system")
self.speak(msg_res)
self.chapter_memory.add(msg_res)
except FunctionCallError as e:
# Catch the function calling error that can be handled by
# the model
error_msg = Msg("system", str(e), "system")
self.speak(error_msg)
self.chapter_memory.add(error_msg)
continue
except Exception as e:
# Catch the function calling error that can not be handled
error_msg = Msg("system", str(e), "system")
self.speak(error_msg)
self.chapter_memory.add(error_msg)
continue
def GetProcess(self):
process = []
t = {}
t['topic'] = "Global Memory"
mems = []
for msg in self.memory.get_memory():
mems.append(msg.serialize())
t['memory'] = "$MEMORYMEMORY$".join(mems)
t['finish'] = "True"
ts = f"{t['topic']}$TOPICTOPIC${t['memory']}$TOPICTOPIC${t['finish']}"
process.append(ts)
for chapter in self.chapter_chain:
t = {}
t['topic'] = f'Chapter {chapter.No}'
mems = []
if chapter.memory is not None:
for msg in self.memory.get_memory():
mems.append(msg.serialize())
t['memory'] = "$MEMORYMEMORY$".join(mems)
t['finish'] = "True" if chapter.focus == CHAPTER_FINISH else "False"
ts = f"{t['topic']}$TOPICTOPIC${t['memory']}$TOPICTOPIC${t['finish']}"
process.append(ts)
returnString = ""
returnString = "$TASKTASK$".join(process)
return returnString

View File

@@ -0,0 +1,10 @@
from agentscope.service.service_toolkit import ServiceToolkit
from loguru import logger
class FlexServiceToolkit(ServiceToolkit):
def Remove(self, service_func_name: str):
if service_func_name in self.service_funcs:
del self.service_funcs[service_func_name]
else:
logger.warning(f"{service_func_name} not found in service_funcs")