01
引言
在上一篇博文中,我们全面概述了AI Agents,讨论了它们的特点、组成、演变、挑战和未来的可能性。
在本文中,我们将探讨如何使用 Python 从零开始构建一个Agent。该Agent能够根据用户输入做出决策、选择适当的工具并执行相应的任务。让我们开始吧!
02
Agent是指一种能够感知环境、做出决策并采取行动以实现特定目标的自主实体。AI Agent的复杂程度各不相同,既有仅对刺激做出反应的简单反应式智能体,也有能够随时间推移不断学习和适应的高级智能体。常见的智能体类型包括:
Reactive Agents: 直接响应环境变化,不具备内部记忆。
Model-Based Agents: 利用内部世界模型进行决策的智能体。
Goal-Based Agents: 以实现特定目标为基础规划行动。
Utility-Based Agents: 基于效用函数评估潜在行动,以实现结果的最大化。
-
Models: 智能体的大脑,负责处理输入信息并做出反应。 -
Tools: 智能体可根据用户请求执行的预定义功能。 -
Toolbox: 智能体可使用的工具集合。 -
System Prompt: 指导智能体处理用户输入并选择正确工具的指令集。
03
-
前提条件
Github: https://github.com/vsingh9076/AI-Agents/tree/main/build-agent-from-scratch
-
Python 环境设置
python -m venv ai_agents_envsource ai_agents_env/bin/activate # On Windows: ai_agents_env\Scripts\activate
pip install -r requirements.txt
-
在本地设置 Ollama
Ollama官网:https://ollama.com/
ollama --version
ollama pull mistral # Replace 'mistral' with the model needed
04
pip install requests termcolor python-dotenv
from termcolor import coloredimport osfrom dotenv import load_dotenvload_dotenv()### Modelsimport requestsimport jsonimport operator
class OllamaModel:def __init__(self, model, system_prompt, temperature=0, stop=None):"""Initializes the OllamaModel with the given parameters.Parameters:model (str): The name of the model to use.system_prompt (str): The system prompt to use.temperature (float): The temperature setting for the model.stop (str): The stop token for the model."""self.model_endpoint = "http://localhost:11434/api/generate"self.temperature = temperatureself.model = modelself.system_prompt = system_promptself.headers = {"Content-Type": "application/json"}self.stop = stopdef generate_text(self, prompt):"""Generates a response from the Ollama model based on the provided prompt.Parameters:prompt (str): The user query to generate a response for.Returns:dict: The response from the model as a dictionary."""payload = {"model": self.model,"format": "json","prompt": prompt,"system": self.system_prompt,"stream": False,"temperature": self.temperature,"stop": self.stop}try:request_response = requests.post(self.model_endpoint,headers=self.headers,data=json.dumps(payload))print("REQUEST RESPONSE", request_response)request_response_json = request_response.json()response = request_response_json['response']response_dict = json.loads(response)print(f"\n\nResponse from Ollama model: {response_dict}")return response_dictexcept requests.RequestException as e:response = {"error": f"Error in invoking model! {str(e)}"}return response
def basic_calculator(input_str):"""Perform a numeric operation on two numbers based on the input string or dictionary.Parameters:input_str (str or dict): Either a JSON string representing a dictionary with keys 'num1', 'num2', and 'operation',or a dictionary directly. Example: '{"num1": 5, "num2": 3, "operation": "add"}'or {"num1": 67869, "num2": 9030393, "operation": "divide"}Returns:str: The formatted result of the operation.Raises:Exception: If an error occurs during the operation (e.g., division by zero).ValueError: If an unsupported operation is requested or input is invalid."""try:# Handle both dictionary and string inputsif isinstance(input_str, dict):input_dict = input_strelse:# Clean and parse the input stringinput_str_clean = input_str.replace("'", "\"")input_str_clean = input_str_clean.strip().strip("\"")input_dict = json.loads(input_str_clean)# Validate required fieldsif not all(key in input_dict for key in ['num1', 'num2', 'operation']):return "Error: Input must contain 'num1', 'num2', and 'operation'"num1 = float(input_dict['num1']) # Convert to float to handle decimal numbersnum2 = float(input_dict['num2'])operation = input_dict['operation'].lower() # Make case-insensitiveexcept (json.JSONDecodeError, KeyError) as e:return "Invalid input format. Please provide valid numbers and operation."except ValueError as e:return "Error: Please provide valid numerical values."# Define the supported operations with error handlingoperations = {'add': operator.add,'plus': operator.add, # Alternative word for add'subtract': operator.sub,'minus': operator.sub, # Alternative word for subtract'multiply': operator.mul,'times': operator.mul, # Alternative word for multiply'divide': operator.truediv,'floor_divide': operator.floordiv,'modulus': operator.mod,'power': operator.pow,'lt': operator.lt,'le': operator.le,'eq': operator.eq,'ne': operator.ne,'ge': operator.ge,'gt': operator.gt}# Check if the operation is supportedif operation not in operations:return f"Unsupported operation: '{operation}'. Supported operations are: {', '.join(operations.keys())}"try:# Special handling for division by zeroif (operation in ['divide', 'floor_divide', 'modulus']) and num2 == 0:return "Error: Division by zero is not allowed"# Perform the operationresult = operations[operation](num1, num2)# Format result based on typeif isinstance(result, bool):result_str = "True" if result else "False"elif isinstance(result, float):# Handle floating point precisionresult_str = f"{result:.6f}".rstrip('0').rstrip('.')else:result_str = str(result)return f"The answer is: {result_str}"except Exception as e:return f"Error during calculation: {str(e)}"def reverse_string(input_string):"""Reverse the given string.Parameters:input_string (str): The string to be reversed.Returns:str: The reversed string."""# Check if input is a stringif not isinstance(input_string, str):return "Error: Input must be a string"# Reverse the string using slicingreversed_string = input_string[::-1]# Format the outputresult = f"The reversed string is: {reversed_string}"return result
class ToolBox:def __init__(self):self.tools_dict = {}def store(self, functions_list):"""Stores the literal name and docstring of each function in the list.Parameters:functions_list (list): List of function objects to store.Returns:dict: Dictionary with function names as keys and their docstrings as values."""for func in functions_list:self.tools_dict[func.__name__] = func.__doc__return self.tools_dictdef tools(self):"""Returns the dictionary created in store as a text string.Returns:str: Dictionary of stored functions and their docstrings as a text string."""tools_str = ""for name, doc in self.tools_dict.items():tools_str += f"{name}: \"{doc}\"\n"return tools_str.strip()
这个类将帮助智能体了解哪些工具可用以及每种工具的具体用途。
agent_system_prompt_template = """You are an intelligent AI assistant with access to specific tools. Your responses must ALWAYS be in this JSON format:{{"tool_choice": "name_of_the_tool","tool_input": "inputs_to_the_tool"}}TOOLS AND WHEN TO USE THEM:1. basic_calculator: Use for ANY mathematical calculations- Input format: {{"num1": number, "num2": number, "operation": "add/subtract/multiply/divide"}}- Supported operations: add/plus, subtract/minus, multiply/times, divide- Example inputs and outputs:Input: "Calculate 15 plus 7"Output: {{"tool_choice": "basic_calculator", "tool_input": {{"num1": 15, "num2": 7, "operation": "add"}}}}Input: "What is 100 divided by 5?"Output: {{"tool_choice": "basic_calculator", "tool_input": {{"num1": 100, "num2": 5, "operation": "divide"}}}}2. reverse_string: Use for ANY request involving reversing text- Input format: Just the text to be reversed as a string- ALWAYS use this tool when user mentions "reverse", "backwards", or asks to reverse text- Example inputs and outputs:Input: "Reverse of 'Howwwww'?"Output: {{"tool_choice": "reverse_string", "tool_input": "Howwwww"}}Input: "What is the reverse of Python?"Output: {{"tool_choice": "reverse_string", "tool_input": "Python"}}3. no tool: Use for general conversation and questions- Example inputs and outputs:Input: "Who are you?"Output: {{"tool_choice": "no tool", "tool_input": "I am an AI assistant that can help you with calculations, reverse text, and answer questions. I can perform mathematical operations and reverse strings. How can I help you today?"}}Input: "How are you?"Output: {{"tool_choice": "no tool", "tool_input": "I'm functioning well, thank you for asking! I'm here to help you with calculations, text reversal, or answer any questions you might have."}}STRICT RULES:1. For questions about identity, capabilities, or feelings:- ALWAYS use "no tool"- Provide a complete, friendly response- Mention your capabilities2. For ANY text reversal request:- ALWAYS use "reverse_string"- Extract ONLY the text to be reversed- Remove quotes, "reverse of", and other extra text3. For ANY math operations:- ALWAYS use "basic_calculator"- Extract the numbers and operation- Convert text numbers to digitsHere is a list of your tools along with their descriptions:{tool_descriptions}Remember: Your response must ALWAYS be valid JSON with "tool_choice" and "tool_input" fields."""
class Agent:def __init__(self, tools, model_service, model_name, stop=None):"""Initializes the agent with a list of tools and a model.Parameters:tools (list): List of tool functions.model_service (class): The model service class with a generate_text method.model_name (str): The name of the model to use."""self.tools = toolsself.model_service = model_serviceself.model_name = model_nameself.stop = stopdef prepare_tools(self):"""Stores the tools in the toolbox and returns their descriptions.Returns:str: Descriptions of the tools stored in the toolbox."""toolbox = ToolBox()toolbox.store(self.tools)tool_descriptions = toolbox.tools()return tool_descriptionsdef think(self, prompt):"""Runs the generate_text method on the model using the system prompt template and tool descriptions.Parameters:prompt (str): The user query to generate a response for.Returns:dict: The response from the model as a dictionary."""tool_descriptions = self.prepare_tools()agent_system_prompt = agent_system_prompt_template.format(tool_descriptions=tool_descriptions)# Create an instance of the model service with the system promptif self.model_service == OllamaModel:model_instance = self.model_service(model=self.model_name,system_prompt=agent_system_prompt,temperature=0,stop=self.stop)else:model_instance = self.model_service(model=self.model_name,system_prompt=agent_system_prompt,temperature=0)# Generate and return the response dictionaryagent_response_dict = model_instance.generate_text(prompt)return agent_response_dictdef work(self, prompt):"""Parses the dictionary returned from think and executes the appropriate tool.Parameters:prompt (str): The user query to generate a response for.Returns:The response from executing the appropriate tool or the tool_input if no matching tool is found."""agent_response_dict = self.think(prompt)tool_choice = agent_response_dict.get("tool_choice")tool_input = agent_response_dict.get("tool_input")for tool in self.tools:if tool.__name__ == tool_choice:response = tool(tool_input)print(colored(response, 'cyan'))returnprint(colored(tool_input, 'cyan'))return
-
prepare_tools: 存储并返回工具说明。 -
think: 根据用户提示决定使用哪种工具。 -
work: 执行所选工具并返回结果。
# Example usageif __name__ == "__main__":"""Instructions for using this agent:Example queries you can try:1. Calculator operations:- "Calculate 15 plus 7"- "What is 100 divided by 5?"- "Multiply 23 and 4"2. String reversal:- "Reverse the word 'hello world'"- "Can you reverse 'Python Programming'?"3. General questions (will get direct responses):- "Who are you?"- "What can you help me with?"Ollama Commands (run these in terminal):- Check available models: 'ollama list'- Check running models: 'ps aux | grep ollama'- List model tags: 'curl http://localhost:11434/api/tags'- Pull a new model: 'ollama pull mistral'- Run model server: 'ollama serve'"""tools = [basic_calculator, reverse_string]# Uncomment below to run with OpenAI# model_service = OpenAIModel# model_name = 'gpt-3.5-turbo'# stop = None# Using Ollama with llama2 modelmodel_service = OllamaModelmodel_name = "llama2" # Can be changed to other models like 'mistral', 'codellama', etc.stop = "<|eot_id|>"agent = Agent(tools=tools, model_service=model_service, model_name=model_name, stop=stop)print("\nWelcome to the AI Agent! Type 'exit' to quit.")print("You can ask me to:")print("1. Perform calculations (e.g., 'Calculate 15 plus 7')")print("2. Reverse strings (e.g., 'Reverse hello world')")print("3. Answer general questions\n")while True:prompt = input("Ask me anything: ")if prompt.lower() == "exit":breakagent.work(prompt)
点击上方小卡片关注我
添加个人微信,进专属粉丝群!

