Skip to main content

Writing a software application using function calls

Open In Colab Open on GitHub

The default way of creating code in Autogen is its built-in code extractor. Although it allows for creating and executing simple scripts fast, that way of creating code is not suitable for developing advanced software applications, according to my experiences. The process of developing an application is mostly the process of introducing changes into existing files rather than creating new files with code. And in my experience, the code extractor is bad at introducing changes as the model often gets lost and can damage existing files.

Properly created functions that can modify code provide us with the ability to have more control over code changes and result in better quality. Additionally, as the scope of possible operations is predefined inside the tools, we can safely use Autogen without Docker, avoiding all the complications related to it.

Requirements

! pip install pyautogen

Set your API Endpoint

import os

import autogen

config_list = [{"model": "gpt-4-turbo-preview", "api_key": os.getenv("OPENAI_API_KEY")}]

Create agents

In this example, we will improve a simple FastAPI application using only dedicated function calls. Let’s create an Engineer agent that will think out and execute code changes, and a user proxy Admin agent, through which we will guide our Engineer.

llm_config = {
"temperature": 0,
"config_list": config_list,
}

engineer = autogen.AssistantAgent(
name="Engineer",
llm_config=llm_config,
system_message="""
I'm Engineer. I'm expert in python programming. I'm executing code tasks required by Admin.
""",
)

user_proxy = autogen.UserProxyAgent(
name="Admin",
human_input_mode="ALWAYS",
code_execution_config=False,
)

Mention, unlike in many other examples, here we don’t need a separate Executor agent to save our code, as that will be done by functions. We also don’t need Docker to be running because of that - which makes the entire process easier.

Next, let’s set up our group chat.

groupchat = autogen.GroupChat(
agents=[engineer, user_proxy],
messages=[],
max_round=500,
speaker_selection_method="round_robin",
enable_clear_history=True,
)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)

Prepare appropriate functions

Let’s go to core of the thing. Prepare functions that provide Engineer with functionality to modify existing code, create new code files, check filesystem and files.

from typing_extensions import Annotated

default_path = "backend_dir/"


@user_proxy.register_for_execution()
@engineer.register_for_llm(description="List files in choosen directory.")
def list_dir(directory: Annotated[str, "Directory to check."]):
files = os.listdir(default_path + directory)
return 0, files


@user_proxy.register_for_execution()
@engineer.register_for_llm(description="Check the contents of a chosen file.")
def see_file(filename: Annotated[str, "Name and path of file to check."]):
with open(default_path + filename, "r") as file:
lines = file.readlines()
formatted_lines = [f"{i+1}:{line}" for i, line in enumerate(lines)]
file_contents = "".join(formatted_lines)

return 0, file_contents


@user_proxy.register_for_execution()
@engineer.register_for_llm(description="Replace old piece of code with new one. Proper indentation is important.")
def modify_code(
filename: Annotated[str, "Name and path of file to change."],
start_line: Annotated[int, "Start line number to replace with new code."],
end_line: Annotated[int, "End line number to replace with new code."],
new_code: Annotated[str, "New piece of code to replace old code with. Remember about providing indents."],
):
with open(default_path + filename, "r+") as file:
file_contents = file.readlines()
file_contents[start_line - 1 : end_line] = [new_code + "\n"]
file.seek(0)
file.truncate()
file.write("".join(file_contents))
return 0, "Code modified"


@user_proxy.register_for_execution()
@engineer.register_for_llm(description="Create a new file with code.")
def create_file_with_code(
filename: Annotated[str, "Name and path of file to create."], code: Annotated[str, "Code to write in the file."]
):
with open(default_path + filename, "w") as file:
file.write(code)
return 0, "File created successfully"

Prepare code to work with

In this example, we will show how AI can extend the functionalities of existing code, as improving existing code is a much more frequent use case in software development than creating a new one. Let’s prepare the initial code on which we will work. That will be a simple FastAPI script that will allow you to calculate today’s stock spread in percentage for CD Project Red, a famous Polish gamedev company. Create a folder called ‘backend_dir’ and place a ‘main.py’ file here with the following content:

# backend_dir/main.py

from fastapi import FastAPI
import yfinance as yf

app = FastAPI()

@app.get("/cdr_daily_spread")
async def calculate_daily_spread():
cdr = yf.Ticker("CDR.WA")
today_data = cdr.history(period="1d")
spread = ((today_data["High"] - today_data["Low"]) / today_data["Low"]) * 100
return spread

Install needed libraries. We can run our API using:

uvicorn main:app --reload

Send a request to ‘localhost:8000/cdr_daily_spread’ to check if it works.

Edit code using agents

Let’s assume we want our agents to extend the functionality of the application. Let’s modify the endpoint to check the spread also for 11bits, another gamedev company, and compare it for both stocks. Also, let’s separate the internal logic into a different file.

Finally, instantiate a chat between the Engineer and the Admin. It will start by exploring the filesystem first, and after that, it will wait for our orders. Then, we will explain the task to the Engineer and ask him to provide a plan of changes first - according to my experience, that greatly increases the quality of LLM responses.

After that, introduce changes with the Engineer one after another. Ask him to correct something or improve the functionality if needed. Do not hesitate to interrupt the tool’s execution if you feel he is going to do something wrong. If errors occur, provide him with the error log and ask him to check out the file to refresh his knowledge about it before actually introducing changes.

chat_result = user_proxy.initiate_chat(
manager,
message="""
You will need to improve app in FastApi. For now, check out all the application files, try to understand it and wait for next instructions.
""",
)
Admin (to chat_manager):


You will need to improve app in FastApi. For now, check out all the application files, try to understand it and wait for next instructions.


--------------------------------------------------------------------------------
Engineer (to chat_manager):

***** Suggested tool Call (call_SA61u9yCLhyXfd9NCV9TAIiM): list_dir *****
Arguments:

{
"directory": "./"
}
*************************************************************************

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation:

>>>>>>>> NO HUMAN INPUT RECEIVED.

>>>>>>>> USING AUTO REPLY...

>>>>>>>> EXECUTING FUNCTION list_dir...
Admin (to chat_manager):

Admin (to chat_manager):

***** Response from calling tool "call_SA61u9yCLhyXfd9NCV9TAIiM" *****
[0, ["main.py", "__pycache__"]]
**********************************************************************

--------------------------------------------------------------------------------
Engineer (to chat_manager):

***** Suggested tool Call (call_IVJNPI12s4fCzysnWjExZjL2): see_file *****
Arguments:

{
"filename": "main.py"
}
*************************************************************************

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation:

>>>>>>>> NO HUMAN INPUT RECEIVED.

>>>>>>>> USING AUTO REPLY...

>>>>>>>> EXECUTING FUNCTION see_file...
Admin (to chat_manager):

Admin (to chat_manager):

***** Response from calling tool "call_IVJNPI12s4fCzysnWjExZjL2" *****
[0, "1:from fastapi import FastAPI\n2:import yfinance as yf\n3:\n4:app = FastAPI()\n5:\n6:@app.get(\"/cdr_daily_spread\")\n7:async def calculate_daily_spread():\n8: cdr = yf.Ticker(\"CDR.WA\")\n9: today_data = cdr.history(period=\"1d\")\n10: spread = ((today_data[\"High\"] - today_data[\"Low\"]) / today_data[\"Low\"]) * 100\n11: return spread"]
**********************************************************************

--------------------------------------------------------------------------------
Engineer (to chat_manager):

The application is a FastAPI application with a single endpoint `/cdr_daily_spread`. This endpoint calculates the daily spread of the stock "CDR.WA" using the `yfinance` library. The spread is calculated as the percentage difference between the high and low prices of the stock for the day.

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: Let's move spread calculation logic to separate file. Create spread_calculation function with ticker argument in it. in our endpoint, we need to compare daily spread for CD Project Red and 11bits Studio - return difference between them. Start with providing plan of changes. Don't do any changes yet.
Admin (to chat_manager):

Let's move spread calculation logic to separate file. Create spread_calculation function with ticker argument in it. in our endpoint, we need to compare daily spread for CD Project Red and 11bits Studio - return difference between them. Start with providing plan of changes. Don't do any changes yet.

--------------------------------------------------------------------------------
Engineer (to chat_manager):

Sure, here is the plan of changes:

1. Create a new Python file named `spread_calculation.py`.
2. In this file, define a function named `calculate_daily_spread` that takes a `ticker` as an argument. This function will contain the logic to calculate the daily spread of a given stock.
3. Modify the `main.py` file to import the `calculate_daily_spread` function from `spread_calculation.py`.
4. Modify the `/cdr_daily_spread` endpoint in `main.py` to calculate the daily spread for both "CDR.WA" (CD Project Red) and "11B.WA" (11bits Studio) by calling the `calculate_daily_spread` function with the respective ticker as argument.
5. The endpoint will then return the difference between the daily spreads of the two stocks.

Please confirm if this plan is correct and I can proceed with the changes.

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: go with the plan
Admin (to chat_manager):

go with the plan

--------------------------------------------------------------------------------
Engineer (to chat_manager):

***** Suggested tool Call (call_cqaVQJ6VQjR7lh114bxJGVfi): create_file_with_code *****
Arguments:

{
"filename": "spread_calculation.py",
"code": "import yfinance as yf\n\ndef calculate_daily_spread(ticker):\n stock = yf.Ticker(ticker)\n today_data = stock.history(period=\"1d\")\n spread = ((today_data[\"High\"] - today_data[\"Low\"]) / today_data[\"Low\"]) * 100\n return spread"
}
**************************************************************************************

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation:

>>>>>>>> NO HUMAN INPUT RECEIVED.

>>>>>>>> USING AUTO REPLY...

>>>>>>>> EXECUTING FUNCTION create_file_with_code...
Admin (to chat_manager):

Admin (to chat_manager):

***** Response from calling tool "call_cqaVQJ6VQjR7lh114bxJGVfi" *****
[0, "File created successfully"]
**********************************************************************

--------------------------------------------------------------------------------
Engineer (to chat_manager):

***** Suggested tool Call (call_qcoMK0RO5bXdY4yQz5RCh4mp): modify_code *****
Arguments:

{
"filename": "main.py",
"start_line": 1,
"end_line": 11,
"new_code": "from fastapi import FastAPI\nfrom spread_calculation import calculate_daily_spread\n\napp = FastAPI()\n\n@app.get(\"/cdr_daily_spread\")\nasync def compare_daily_spread():\n cdr_spread = calculate_daily_spread(\"CDR.WA\")\n bits_spread = calculate_daily_spread(\"11B.WA\")\n return cdr_spread - bits_spread"
}
****************************************************************************

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: Do not change whole the file. concentrate on the concrete lines.
Admin (to chat_manager):

Admin (to chat_manager):

***** Response from calling tool "call_qcoMK0RO5bXdY4yQz5RCh4mp" *****
USER INTERRUPTED
**********************************************************************

--------------------------------------------------------------------------------
Do not change whole the file. concentrate on the concrete lines.

--------------------------------------------------------------------------------
Engineer (to chat_manager):

***** Suggested tool Call (call_hz1v2vEPPxKHeygjqs2lc8ii): modify_code *****
Arguments:

{
"filename": "main.py",
"start_line": 1,
"end_line": 2,
"new_code": "from fastapi import FastAPI\nfrom spread_calculation import calculate_daily_spread\nimport yfinance as yf"
}
****************************************************************************

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation:

>>>>>>>> NO HUMAN INPUT RECEIVED.

>>>>>>>> USING AUTO REPLY...

>>>>>>>> EXECUTING FUNCTION modify_code...
Admin (to chat_manager):

Admin (to chat_manager):

***** Response from calling tool "call_hz1v2vEPPxKHeygjqs2lc8ii" *****
[0, "Code modified"]
**********************************************************************

--------------------------------------------------------------------------------
Engineer (to chat_manager):

***** Suggested tool Call (call_1ABNdk6AATaVvn8UqLKBD5ZR): modify_code *****
Arguments:

{
"filename": "main.py",
"start_line": 6,
"end_line": 11,
"new_code": "@app.get(\"/cdr_daily_spread\")\nasync def compare_daily_spread():\n cdr_spread = calculate_daily_spread(\"CDR.WA\")\n bits_spread = calculate_daily_spread(\"11B.WA\")\n return cdr_spread - bits_spread"
}
****************************************************************************

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: Check out file first to get actual lines position. Also, change endpoint name.
Admin (to chat_manager):

Admin (to chat_manager):

***** Response from calling tool "call_1ABNdk6AATaVvn8UqLKBD5ZR" *****
USER INTERRUPTED
**********************************************************************

--------------------------------------------------------------------------------
Check out file first to get actual lines position. Also, change endpoint name.

--------------------------------------------------------------------------------
Engineer (to chat_manager):

***** Suggested tool Call (call_2vlnu0RJ11qg6ygGT6l2tS3Q): see_file *****
Arguments:

{
"filename": "main.py"
}
*************************************************************************

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation:

>>>>>>>> NO HUMAN INPUT RECEIVED.

>>>>>>>> USING AUTO REPLY...

>>>>>>>> EXECUTING FUNCTION see_file...
Admin (to chat_manager):

Admin (to chat_manager):

***** Response from calling tool "call_2vlnu0RJ11qg6ygGT6l2tS3Q" *****
[0, "1:from fastapi import FastAPI\n2:from spread_calculation import calculate_daily_spread\n3:import yfinance as yf\n4:\n5:app = FastAPI()\n6:\n7:@app.get(\"/cdr_daily_spread\")\n8:async def calculate_daily_spread():\n9: cdr = yf.Ticker(\"CDR.WA\")\n10: today_data = cdr.history(period=\"1d\")\n11: spread = ((today_data[\"High\"] - today_data[\"Low\"]) / today_data[\"Low\"]) * 100\n12: return spread"]
**********************************************************************

--------------------------------------------------------------------------------
Engineer (to chat_manager):

***** Suggested tool Call (call_gNwVSM2u33Ej89Yc2IkSAovu): modify_code *****
Arguments:

{
"filename": "main.py",
"start_line": 7,
"end_line": 12,
"new_code": "@app.get(\"/compare_daily_spread\")\nasync def compare_daily_spread():\n cdr_spread = calculate_daily_spread(\"CDR.WA\")\n bits_spread = calculate_daily_spread(\"11B.WA\")\n return cdr_spread - bits_spread"
}
****************************************************************************

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation:

>>>>>>>> NO HUMAN INPUT RECEIVED.

>>>>>>>> USING AUTO REPLY...

>>>>>>>> EXECUTING FUNCTION modify_code...
Admin (to chat_manager):

Admin (to chat_manager):

***** Response from calling tool "call_gNwVSM2u33Ej89Yc2IkSAovu" *****
[0, "Code modified"]
**********************************************************************

--------------------------------------------------------------------------------
Engineer (to chat_manager):

The changes have been made successfully. Now, the FastAPI application has an endpoint `/compare_daily_spread` which calculates and returns the difference between the daily spreads of "CDR.WA" (CD Project Red) and "11B.WA" (11bits Studio). The calculation logic is now in a separate file `spread_calculation.py`.

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: Good. But add in return some addition description to be understandable which stock change is bigger. Remember to check out main.py first to actualize your knowledge.
Admin (to chat_manager):

Good. But add in return some addition description to be understandable which stock change is bigger. Remember to check out main.py first to actualize your knowledge.

--------------------------------------------------------------------------------
Engineer (to chat_manager):

***** Suggested tool Call (call_1Clo7beP49gWm1oXQXK7W6RH): see_file *****
Arguments:

{
"filename": "main.py"
}
*************************************************************************

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation:

>>>>>>>> NO HUMAN INPUT RECEIVED.

>>>>>>>> USING AUTO REPLY...

>>>>>>>> EXECUTING FUNCTION see_file...
Admin (to chat_manager):

Admin (to chat_manager):

***** Response from calling tool "call_1Clo7beP49gWm1oXQXK7W6RH" *****
[0, "1:from fastapi import FastAPI\n2:from spread_calculation import calculate_daily_spread\n3:import yfinance as yf\n4:\n5:app = FastAPI()\n6:\n7:@app.get(\"/compare_daily_spread\")\n8:async def compare_daily_spread():\n9: cdr_spread = calculate_daily_spread(\"CDR.WA\")\n10: bits_spread = calculate_daily_spread(\"11B.WA\")\n11: return cdr_spread - bits_spread\n"]
**********************************************************************

--------------------------------------------------------------------------------
Engineer (to chat_manager):

***** Suggested tool Call (call_Cl6jBUwvfa8JGbyldfrzYfiB): modify_code *****
Arguments:

{
"filename": "main.py",
"start_line": 7,
"end_line": 11,
"new_code": "@app.get(\"/compare_daily_spread\")\nasync def compare_daily_spread():\n cdr_spread = calculate_daily_spread(\"CDR.WA\")\n bits_spread = calculate_daily_spread(\"11B.WA\")\n spread_difference = cdr_spread - bits_spread\n if spread_difference > 0:\n return {'message': 'CD Project Red has a larger daily spread', 'difference': spread_difference}\n elif spread_difference < 0:\n return {'message': '11bits Studio has a larger daily spread', 'difference': -spread_difference}\n else:\n return {'message': 'Both stocks have the same daily spread', 'difference': 0}"
}
****************************************************************************

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation:

>>>>>>>> NO HUMAN INPUT RECEIVED.

>>>>>>>> USING AUTO REPLY...

>>>>>>>> EXECUTING FUNCTION modify_code...
Admin (to chat_manager):

Admin (to chat_manager):

***** Response from calling tool "call_Cl6jBUwvfa8JGbyldfrzYfiB" *****
[0, "Code modified"]
**********************************************************************

--------------------------------------------------------------------------------
Engineer (to chat_manager):

The changes have been made successfully. Now, the `/compare_daily_spread` endpoint returns a dictionary with a message indicating which stock (CD Project Red or 11bits Studio) has a larger daily spread and the absolute difference between the daily spreads. If both stocks have the same daily spread, the message will indicate this and the difference will be 0.

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: ValueError: The truth value of a Series is ambiguous.
Admin (to chat_manager):

ValueError: The truth value of a Series is ambiguous.

--------------------------------------------------------------------------------
Engineer (to chat_manager):

The error message indicates that the comparison operation is being performed on a pandas Series object, which is ambiguous. This is likely because the `calculate_daily_spread` function is returning a Series object instead of a single value.

To fix this, we need to ensure that the `calculate_daily_spread` function returns a single value. We can do this by taking the first (and only) value from the Series object.

Here is the plan of changes:

1. Modify the `calculate_daily_spread` function in `spread_calculation.py` to return only the first value from the `spread` Series.
2. No changes are required in `main.py` as the returned value from `calculate_daily_spread` function will now be a single value.

Please confirm if this plan is correct and I can proceed with the changes.

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: Cool. Remember to check out file before introducing modifications.
Admin (to chat_manager):

Cool. Remember to check out file before introducing modifications.

--------------------------------------------------------------------------------
Engineer (to chat_manager):

***** Suggested tool Call (call_4wPhGFFW8pt8B28nVhVvWztN): see_file *****
Arguments:

{
"filename": "spread_calculation.py"
}
*************************************************************************

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation:

>>>>>>>> NO HUMAN INPUT RECEIVED.

>>>>>>>> USING AUTO REPLY...

>>>>>>>> EXECUTING FUNCTION see_file...
Admin (to chat_manager):

Admin (to chat_manager):

***** Response from calling tool "call_4wPhGFFW8pt8B28nVhVvWztN" *****
[0, "1:import yfinance as yf\n2:\n3:def calculate_daily_spread(ticker):\n4: stock = yf.Ticker(ticker)\n5: today_data = stock.history(period=\"1d\")\n6: spread = ((today_data[\"High\"] - today_data[\"Low\"]) / today_data[\"Low\"]) * 100\n7: return spread"]
**********************************************************************

--------------------------------------------------------------------------------
Engineer (to chat_manager):

***** Suggested tool Call (call_DL0p37GzppRSmoqFaOOLSNaI): modify_code *****
Arguments:

{
"filename": "spread_calculation.py",
"start_line": 3,
"end_line": 7,
"new_code": "def calculate_daily_spread(ticker):\n stock = yf.Ticker(ticker)\n today_data = stock.history(period=\"1d\")\n spread = ((today_data[\"High\"] - today_data[\"Low\"]) / today_data[\"Low\"]) * 100\n return spread.values[0]"
}
****************************************************************************

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation:

>>>>>>>> NO HUMAN INPUT RECEIVED.

>>>>>>>> USING AUTO REPLY...

>>>>>>>> EXECUTING FUNCTION modify_code...
Admin (to chat_manager):

Admin (to chat_manager):

***** Response from calling tool "call_DL0p37GzppRSmoqFaOOLSNaI" *****
[0, "Code modified"]
**********************************************************************

--------------------------------------------------------------------------------
Engineer (to chat_manager):

The changes have been made successfully. Now, the `calculate_daily_spread` function in `spread_calculation.py` returns a single value instead of a pandas Series. This should resolve the ValueError you were encountering.

--------------------------------------------------------------------------------
Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: exit

Result

Finally, our agents modified a code so it looks like this:

# backend_dir/main.py

from fastapi import FastAPI
from spread_calculation import calculate_daily_spread
import yfinance as yf

app = FastAPI()

@app.get("/compare_daily_spread")
async def compare_daily_spread():
cdr_spread = calculate_daily_spread("CDR.WA")
bits_spread = calculate_daily_spread("11B.WA")
spread_difference = cdr_spread - bits_spread
if spread_difference > 0:
return {'message': 'CD Project Red has a larger daily spread', 'difference': spread_difference}
elif spread_difference < 0:
return {'message': '11bits Studio has a larger daily spread', 'difference': -spread_difference}
else:
return {'message': 'Both stocks have the same daily spread', 'difference': 0}


# backend_dir/spread_calculation.py

import yfinance as yf

def calculate_daily_spread(ticker):
stock = yf.Ticker(ticker)
today_data = stock.history(period="1d")
spread = ((today_data["High"] - today_data["Low"]) / today_data["Low"]) * 100
return spread.values[0]

You can check out work of application with Postman or curl and see the next output:

{
"message": "11bits Studio has a larger daily spread",
"difference": 1.7968083865943187
}