跳到主要内容

Kubernetes Pod 命令行代码执行器

Open In Colab Open on GitHub

autogen.coding.kubernetes模块中的PodCommandLineCodeExecutor设计用于在Kubernetes中使用pod执行代码块。它的功能类似于DockerCommandLineCodeExecutor,但专门在Kubernetes环境中创建容器。

使用PodCommandLineCodeExecutor有两个条件。

  • 访问Kubernetes集群
  • 安装 autogen 并附带额外需求 'pyautogen[kubernetes]'

对于本地开发和测试,本文档使用Minikube集群。

Minikube 是一个允许你在本地机器上运行单节点 Kubernetes 集群的工具。你可以参考以下链接进行 Minikube 的安装和设置。

🔗 https://minikube.sigs.k8s.io/docs/start/

访问kubernetes集群

有四种选项通过PodCommandLineCodeExecutor访问kubernetes API服务器。

  • 默认的kubeconfig文件路径: ~/.kube/config
  • 使用PodCommandLineCodeExecutorkube_config_file参数提供自定义的kubeconfig文件路径。
  • 使用KUBECONFIG环境变量设置kubeconfig文件路径。
  • 提供来自Kubernetes ServiceAccount的令牌,具有足够的权限

通常,如果kubeconfig文件位于~/.kube/config,则无需在参数或环境变量中提供kubeconfig文件路径。

提供ServiceAccount Token的教程在最后一节

示例

为了使用基于Kubernetes Pod的代码执行器,你需要安装Kubernetes Python SDK。

您可以通过运行以下命令来实现:

pip install 'kubernetes>=27'

或者,您可以安装带有Kubernetes额外功能的功能:

pip install 'autogen-agentchat[kubernetes]~=0.2'

要提供kubeconfig文件路径的环境变量,可以使用os.environ["KUBECONFIG"]来添加。

import os

# Set the KUBECONFIG environment variable
# if the kubeconfig file is not in the default location(~/.kube/config).
os.environ["KUBECONFIG"] = "path/to/your/kubeconfig"
from autogen.coding import CodeBlock
from autogen.coding.kubernetes import PodCommandLineCodeExecutor
with PodCommandLineCodeExecutor(
namespace="default",
# kube_config_file="kubeconfig/file/path" # If you have another kubeconfig file, you can add it on kube_config_file argument
) as executor:
print(
executor.execute_code_blocks(
# Example of executing a simple Python code block within a Kubernetes pod.
code_blocks=[
CodeBlock(language="python", code="print('Hello, World!')"),
]
)
)
exit_code=0 output='Hello, World!\n' code_file='/workspace/tmp_code_07da107bb575cc4e02b0e1d6d99cc204.py'

使用上下文管理器(with 语句),由 PodCommandLineCodeExecutor 创建的 pod 在任务完成后会自动删除。

虽然在使用上下文管理器时,pod会自动删除,但有时你可能需要手动删除它。你可以使用stop()方法来实现,如下所示:

executor = PodCommandLineCodeExecutor(namespace="default")
%%bash
# This command lists all pods in the default namespace.
# The default pod name follows the format autogen-code-exec-{uuid.uuid4()}.
kubectl get pod -n default
NAME                                                     READY   STATUS    RESTARTS   AGE
autogen-code-exec-afd217ac-f77b-4ede-8c53-1297eca5ec64 1/1 Running 0 10m
%%bash
# This command shows container's image in the pod.
# The default container image is python:3-slim
kubectl get pod autogen-code-exec-afd217ac-f77b-4ede-8c53-1297eca5ec64 -o jsonpath={.spec.containers[0].image}
python:3-slim
executor.stop()

要为代码执行器pod使用不同的容器镜像,请使用image参数指定所需的镜像标签。

PodCommandLineCodeExecutor 有一个默认的执行策略,允许执行Python和Shell脚本代码块。您可以通过execution_policies参数启用其他语言。

with PodCommandLineCodeExecutor(
image="node:22-alpine", # Specifies the runtime environments using a container image
namespace="default",
work_dir="./app", # Directory within the container where code block files are stored
timeout=10, # Timeout in seconds for pod creation and code block execution (default is 60 seconds)
execution_policies={
"javascript": True
}, # Enable execution of Javascript code blocks by updating execution policies
) as executor:
print(
executor.execute_code_blocks(
code_blocks=[
CodeBlock(language="javascript", code="console.log('Hello, World!')"),
]
)
)
exit_code=0 output='Hello, World!\n' code_file='app/tmp_code_8c34c8586cb47943728afe1297b7a51c.js'

如果你想为执行器pod应用自定义设置,例如 注释、环境变量、命令、卷等,你可以 使用kubernetes.client.V1Pod 格式提供自定义的pod规范。

container_name 参数也应该提供,因为 PodCommandLineCodeExecutor 不会自动识别将要执行代码块的容器。

from kubernetes import client

pod = client.V1Pod(
metadata=client.V1ObjectMeta(name="abcd", namespace="default", annotations={"sidecar.istio.io/inject": "false"}),
spec=client.V1PodSpec(
restart_policy="Never",
containers=[
client.V1Container(
args=["-c", "while true;do sleep 5; done"],
command=["/bin/sh"],
name="abcd", # container name where code blocks will be executed should be provided using `container_name` argument
image="python:3.11-slim",
env=[
client.V1EnvVar(name="TEST", value="TEST"),
client.V1EnvVar(
name="POD_NAME",
value_from=client.V1EnvVarSource(
field_ref=client.V1ObjectFieldSelector(field_path="metadata.name")
),
),
],
)
],
),
)
with PodCommandLineCodeExecutor(
pod_spec=pod, # custom executor pod spec
container_name="abcd", # To use custom executor pod spec, container_name where code block will be executed should be specified
work_dir="/autogen",
timeout=60,
) as executor:
print(
executor.execute_code_blocks(
code_blocks=[
CodeBlock(language="python", code="print('Hello, World!')"),
]
)
)
print(
executor.execute_code_blocks(
code_blocks=[
CodeBlock(
code="echo $TEST $POD_NAME", language="bash"
), # echo environment variables specified in pod_spec
]
)
)
exit_code=0 output='Hello, World!\n' code_file='/autogen/tmp_code_07da107bb575cc4e02b0e1d6d99cc204.py'
exit_code=0 output='TEST abcd\n' code_file='/autogen/tmp_code_202399627ea7fb8d8e816f4910b7f87b.sh'

与AutoGen代理集成

PodCommandLineCodeExecutor 可以与 Agents 集成。

from autogen import config_list_from_json

config_list = config_list_from_json(
env_or_file="OAI_CONFIG_LIST",
)
from autogen import ConversableAgent

# The code writer agent's system message is to instruct the LLM on how to
# use the code executor with python or shell script code
code_writer_system_message = """
You have been given coding capability to solve tasks using Python code.
In the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute.
1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself.
2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly.
Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill.
When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user.
If you want the user to save the code in a file before executing it, put # filename: <filename> inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user.
"""
with PodCommandLineCodeExecutor(namespace="default") as executor:

code_executor_agent = ConversableAgent(
name="code_executor_agent",
llm_config=False,
code_execution_config={
"executor": executor,
},
human_input_mode="NEVER",
)

code_writer_agent = ConversableAgent(
"code_writer",
system_message=code_writer_system_message,
llm_config={"config_list": config_list},
code_execution_config=False, # Turn off code execution for this agent.
max_consecutive_auto_reply=2,
human_input_mode="NEVER",
)

chat_result = code_executor_agent.initiate_chat(
code_writer_agent, message="Write Python code to calculate the moves of disk on tower of hanoi with 10 disks"
)
code_executor_agent (to code_writer):

Write Python code to calculate the moves of disk on tower of hanoi with 3 disks

--------------------------------------------------------------------------------
code_writer (to code_executor_agent):

The problem of the Tower of Hanoi with 3 disks involves moving the disks from one peg to another, following these rules:
1. Only one disk can be moved at a time.
2. Each move consists of taking the upper disk from one of the stacks and placing it on top of another stack or on an empty peg.
3. No disk may be placed on top of a smaller disk.

In the solution, I will use a recursive function to calculate the moves and print them out. Here's the Python code to accomplish this:

```python
def tower_of_hanoi(n, from_rod, to_rod, aux_rod):
if n == 1:
print(f"Move disk 1 from rod {from_rod} to rod {to_rod}")
return
tower_of_hanoi(n-1, from_rod, aux_rod, to_rod)
print(f"Move disk {n} from rod {from_rod} to rod {to_rod}")
tower_of_hanoi(n-1, aux_rod, to_rod, from_rod)

n = 3 # Number of disks
tower_of_hanoi(n, 'A', 'C', 'B') # A, B and C are names of the rods
```

This script defines a function `tower_of_hanoi` that will print out each move necessary to solve the Tower of Hanoi problem with the specified number of disks `n`. This specific setup will solve for 3 disks moving from rod 'A' to rod 'C' with the help of rod 'B'.

--------------------------------------------------------------------------------

>>>>>>>> EXECUTING CODE BLOCK (inferred language is python)...
code_executor_agent (to code_writer):

exitcode: 0 (execution succeeded)
Code output: Move disk 1 from rod A to rod C
Move disk 2 from rod A to rod B
Move disk 1 from rod C to rod B
Move disk 3 from rod A to rod C
Move disk 1 from rod B to rod A
Move disk 2 from rod B to rod C
Move disk 1 from rod A to rod C


--------------------------------------------------------------------------------
code_writer (to code_executor_agent):

The execution of the provided code successfully calculated and printed the moves for solving the Tower of Hanoi with 3 disks. Here are the steps it performed:

1. Move disk 1 from rod A to rod C.
2. Move disk 2 from rod A to rod B.
3. Move disk 1 from rod C to rod B.
4. Move disk 3 from rod A to rod C.
5. Move disk 1 from rod B to rod A.
6. Move disk 2 from rod B to rod C.
7. Move disk 1 from rod A to rod C.

This sequence effectively transfers all disks from rod A to rod C using rod B as an auxiliary, following the rules of the Tower of Hanoi puzzle. If you have any more tasks or need further explanation, feel free to ask!

--------------------------------------------------------------------------------
code_executor_agent (to code_writer):



--------------------------------------------------------------------------------
import pprint

pprint.pprint(chat_result)
ChatResult(chat_id=None,
chat_history=[{'content': 'Write Python code to calculate the moves '
'of disk on tower of hanoi with 3 disks',
'name': 'code_executor_agent',
'role': 'assistant'},
{'content': 'The problem of the Tower of Hanoi with 3 '
'disks involves moving the disks from one '
'peg to another, following these rules:\n'
'1. Only one disk can be moved at a '
'time.\n'
'2. Each move consists of taking the '
'upper disk from one of the stacks and '
'placing it on top of another stack or on '
'an empty peg.\n'
'3. No disk may be placed on top of a '
'smaller disk.\n'
'\n'
'In the solution, I will use a recursive '
'function to calculate the moves and '
"print them out. Here's the Python code "
'to accomplish this:\n'
'\n'
'```python\n'
'def tower_of_hanoi(n, from_rod, to_rod, '
'aux_rod):\n'
' if n == 1:\n'
' print(f"Move disk 1 from rod '
'{from_rod} to rod {to_rod}")\n'
' return\n'
' tower_of_hanoi(n-1, from_rod, '
'aux_rod, to_rod)\n'
' print(f"Move disk {n} from rod '
'{from_rod} to rod {to_rod}")\n'
' tower_of_hanoi(n-1, aux_rod, to_rod, '
'from_rod)\n'
'\n'
'n = 3 # Number of disks\n'
"tower_of_hanoi(n, 'A', 'C', 'B') # A, B "
'and C are names of the rods\n'
'```\n'
'\n'
'This script defines a function '
'`tower_of_hanoi` that will print out '
'each move necessary to solve the Tower '
'of Hanoi problem with the specified '
'number of disks `n`. This specific setup '
'will solve for 3 disks moving from rod '
"'A' to rod 'C' with the help of rod 'B'.",
'name': 'code_writer',
'role': 'user'},
{'content': 'exitcode: 0 (execution succeeded)\n'
'Code output: Move disk 1 from rod A to '
'rod C\n'
'Move disk 2 from rod A to rod B\n'
'Move disk 1 from rod C to rod B\n'
'Move disk 3 from rod A to rod C\n'
'Move disk 1 from rod B to rod A\n'
'Move disk 2 from rod B to rod C\n'
'Move disk 1 from rod A to rod C\n',
'name': 'code_executor_agent',
'role': 'assistant'},
{'content': 'The execution of the provided code '
'successfully calculated and printed the '
'moves for solving the Tower of Hanoi '
'with 3 disks. Here are the steps it '
'performed:\n'
'\n'
'1. Move disk 1 from rod A to rod C.\n'
'2. Move disk 2 from rod A to rod B.\n'
'3. Move disk 1 from rod C to rod B.\n'
'4. Move disk 3 from rod A to rod C.\n'
'5. Move disk 1 from rod B to rod A.\n'
'6. Move disk 2 from rod B to rod C.\n'
'7. Move disk 1 from rod A to rod C.\n'
'\n'
'This sequence effectively transfers all '
'disks from rod A to rod C using rod B as '
'an auxiliary, following the rules of the '
'Tower of Hanoi puzzle. If you have any '
'more tasks or need further explanation, '
'feel free to ask!',
'name': 'code_writer',
'role': 'user'},
{'content': '',
'name': 'code_executor_agent',
'role': 'assistant'}],
summary='',
cost={'usage_excluding_cached_inference': {'total_cost': 0},
'usage_including_cached_inference': {'gpt-4-turbo-2024-04-09': {'completion_tokens': 499,
'cost': 0.0269,
'prompt_tokens': 1193,
'total_tokens': 1692},
'total_cost': 0.0269}},
human_input=[])

使用ServiceAccount令牌

如果PodCommandLineCodeExecutor实例在Kubernetes Pod内部运行,它可以使用从ServiceAccount生成的令牌来访问Kubernetes API服务器。

PodCommandLineCodeExecutor 需要以下权限:对于 pods 资源的 creategetdelete 动词,以及对于 pods/statuspods/exec 资源的 get 动词。

您可以使用kubectl创建ServiceAccount、ClusterRole和RoleBinding,如下所示:

%%bash
# Create ServiceAccount on default namespace
kubectl create sa autogen-executor-sa
serviceaccount/autogen-executor-sa created
%%bash
# Create ClusterRole that has sufficient permissions
kubectl create clusterrole autogen-executor-role \
--verb=get,create,delete --resource=pods,pods/status,pods/exec
clusterrole.rbac.authorization.k8s.io/autogen-executor-role created
%%bash
# Create RoleBinding that binds ClusterRole and ServiceAccount
kubectl create rolebinding autogen-executor-rolebinding \
--clusterrole autogen-executor-role --serviceaccount default:autogen-executor-sa
rolebinding.rbac.authorization.k8s.io/autogen-executor-rolebinding created

可以使用以下命令启动带有先前创建的ServiceAccount的pod。

%%bash
# create pod with serviceaccount
kubectl run autogen-executor --image python:3 \
--overrides='{"spec":{"serviceAccount": "autogen-executor-sa"}}' \
-- bash -c 'pip install pyautogen[kubernetes] && sleep inifinity'
pod/autogen-executor created

你可以在autogen-executor Pod的Python解释器进程中执行PodCommandLineCodeExecutor

它使用从autogen-executor-sa ServiceAccount生成的令牌创建新的pod以执行代码。

%%bash
kubectl exec autogen-executor -it -- python
from autogen.coding import CodeBlock
from autogen.coding.kubernetes import PodCommandLineCodeExecutor

# PodCommandLineCodeExecutor uses token generated from ServiceAccount by kubernetes incluster config
with PodCommandLineCodeExecutor() as executor:
print(
executor.execute_code_blocks(
code_blocks=[
CodeBlock(language="python", code="print('Hello, World!')"),
]
)
)
kube_config_path not provided and default location (~/.kube/config) does not exist. Using inCluster Config. This might not work.
exit_code=0 output='Hello, World!\n' code_file='/workspace/tmp_code_07da107bb575cc4e02b0e1d6d99cc204.py'