mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-08 15:05:17 +02:00
Merge pull request #22 from geekan/main
feat: Merge geekan/MetaGPT/main
This commit is contained in:
commit
62f0745b46
17 changed files with 1143 additions and 0 deletions
109
docs/install/cli_install.md
Normal file
109
docs/install/cli_install.md
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
## Traditional Command Line Installation
|
||||
|
||||
### Support System and version
|
||||
| System Version | Python Version | Supported |
|
||||
| ---- | ---- | ----- |
|
||||
| macOS 13.x | python 3.9 | Yes |
|
||||
| Windows 11 | python 3.9 | Yes |
|
||||
| Ubuntu 22.04 | python 3.9 | Yes |
|
||||
|
||||
### Detail Installation
|
||||
```bash
|
||||
# Step 1: Ensure that NPM is installed on your system. Then install mermaid-js. (If you don't have npm in your computer, please go to the Node.js official website to install Node.js https://nodejs.org/ and then you will have npm tool in your computer.)
|
||||
npm --version
|
||||
sudo npm install -g @mermaid-js/mermaid-cli
|
||||
|
||||
# Step 2: Ensure that Python 3.9+ is installed on your system. You can check this by using:
|
||||
python3 --version
|
||||
|
||||
# Step 3: Clone the repository to your local machine, and install it.
|
||||
git clone https://github.com/geekan/MetaGPT.git
|
||||
cd MetaGPT
|
||||
pip install -e.
|
||||
```
|
||||
|
||||
**Note:**
|
||||
|
||||
- If already have Chrome, Chromium, or MS Edge installed, you can skip downloading Chromium by setting the environment variable
|
||||
`PUPPETEER_SKIP_CHROMIUM_DOWNLOAD` to `true`.
|
||||
|
||||
- Some people are [having issues](https://github.com/mermaidjs/mermaid.cli/issues/15) installing this tool globally. Installing it locally is an alternative solution,
|
||||
|
||||
```bash
|
||||
npm install @mermaid-js/mermaid-cli
|
||||
```
|
||||
|
||||
- don't forget to the configuration for mmdc in config.yml
|
||||
|
||||
```yml
|
||||
PUPPETEER_CONFIG: "./config/puppeteer-config.json"
|
||||
MMDC: "./node_modules/.bin/mmdc"
|
||||
```
|
||||
|
||||
- if `pip install -e.` fails with error `[Errno 13] Permission denied: '/usr/local/lib/python3.11/dist-packages/test-easy-install-13129.write-test'`, try instead running `pip install -e. --user`
|
||||
|
||||
- To convert Mermaid charts to SVG, PNG, and PDF formats. In addition to the Node.js version of Mermaid-CLI, you now have the option to use Python version Playwright, pyppeteer or mermaid.ink for this task.
|
||||
|
||||
- Playwright
|
||||
- **Install Playwright**
|
||||
|
||||
```bash
|
||||
pip install playwright
|
||||
```
|
||||
|
||||
- **Install the Required Browsers**
|
||||
|
||||
to support PDF conversion, please install Chrominum.
|
||||
|
||||
```bash
|
||||
playwright install --with-deps chromium
|
||||
```
|
||||
|
||||
- **modify `config.yaml`**
|
||||
|
||||
uncomment MERMAID_ENGINE from config.yaml and change it to `playwright`
|
||||
|
||||
```yaml
|
||||
MERMAID_ENGINE: playwright
|
||||
```
|
||||
|
||||
- pyppeteer
|
||||
- **Install pyppeteer**
|
||||
|
||||
```bash
|
||||
pip install pyppeteer
|
||||
```
|
||||
|
||||
- **Use your own Browsers**
|
||||
|
||||
pyppeteer allows you use installed browsers, please set the following envirment
|
||||
|
||||
```bash
|
||||
export PUPPETEER_EXECUTABLE_PATH = /path/to/your/chromium or edge or chrome
|
||||
```
|
||||
|
||||
please do not use this command to install browser, it is too old
|
||||
|
||||
```bash
|
||||
pyppeteer-install
|
||||
```
|
||||
|
||||
- **modify `config.yaml`**
|
||||
|
||||
uncomment MERMAID_ENGINE from config.yaml and change it to `pyppeteer`
|
||||
|
||||
```yaml
|
||||
MERMAID_ENGINE: pyppeteer
|
||||
```
|
||||
|
||||
- mermaid.ink
|
||||
- **modify `config.yaml`**
|
||||
|
||||
uncomment MERMAID_ENGINE from config.yaml and change it to `ink`
|
||||
|
||||
```yaml
|
||||
MERMAID_ENGINE: ink
|
||||
```
|
||||
|
||||
Note: this method does not support pdf export.
|
||||
|
||||
43
docs/install/cli_install_cn.md
Normal file
43
docs/install/cli_install_cn.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
## 命令行安装
|
||||
|
||||
### 支持的系统和版本
|
||||
| 系统版本 | Python 版本 | 是否支持 |
|
||||
| ---- | ---- | ----- |
|
||||
| macOS 13.x | python 3.9 | 是 |
|
||||
| Windows 11 | python 3.9 | 是 |
|
||||
| Ubuntu 22.04 | python 3.9 | 是 |
|
||||
|
||||
### 详细安装
|
||||
|
||||
```bash
|
||||
# 第 1 步:确保您的系统上安装了 NPM。并使用npm安装mermaid-js
|
||||
npm --version
|
||||
sudo npm install -g @mermaid-js/mermaid-cli
|
||||
|
||||
# 第 2 步:确保您的系统上安装了 Python 3.9+。您可以使用以下命令进行检查:
|
||||
python --version
|
||||
|
||||
# 第 3 步:克隆仓库到您的本地机器,并进行安装。
|
||||
git clone https://github.com/geekan/MetaGPT.git
|
||||
cd MetaGPT
|
||||
pip install -e.
|
||||
```
|
||||
|
||||
**注意:**
|
||||
|
||||
- 如果已经安装了Chrome、Chromium或MS Edge,可以通过将环境变量`PUPPETEER_SKIP_CHROMIUM_DOWNLOAD`设置为`true`来跳过下载Chromium。
|
||||
|
||||
- 一些人在全局安装此工具时遇到问题。在本地安装是替代解决方案,
|
||||
|
||||
```bash
|
||||
npm install @mermaid-js/mermaid-cli
|
||||
```
|
||||
|
||||
- 不要忘记在config.yml中为mmdc配置配置,
|
||||
|
||||
```yml
|
||||
PUPPETEER_CONFIG: "./config/puppeteer-config.json"
|
||||
MMDC: "./node_modules/.bin/mmdc"
|
||||
```
|
||||
|
||||
- 如果`pip install -e.`失败并显示错误`[Errno 13] Permission denied: '/usr/local/lib/python3.11/dist-packages/test-easy-install-13129.write-test'`,请尝试使用`pip install -e. --user`运行。
|
||||
44
docs/install/docker_install.md
Normal file
44
docs/install/docker_install.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
## Docker Installation
|
||||
|
||||
### Use default MetaGPT image
|
||||
|
||||
```bash
|
||||
# Step 1: Download metagpt official image and prepare config.yaml
|
||||
docker pull metagpt/metagpt:latest
|
||||
mkdir -p /opt/metagpt/{config,workspace}
|
||||
docker run --rm metagpt/metagpt:latest cat /app/metagpt/config/config.yaml > /opt/metagpt/config/key.yaml
|
||||
vim /opt/metagpt/config/key.yaml # Change the config
|
||||
|
||||
# Step 2: Run metagpt demo with container
|
||||
docker run --rm \
|
||||
--privileged \
|
||||
-v /opt/metagpt/config/key.yaml:/app/metagpt/config/key.yaml \
|
||||
-v /opt/metagpt/workspace:/app/metagpt/workspace \
|
||||
metagpt/metagpt:latest \
|
||||
python3 startup.py "Write a cli snake game"
|
||||
|
||||
# You can also start a container and execute commands in it
|
||||
docker run --name metagpt -d \
|
||||
--privileged \
|
||||
-v /opt/metagpt/config/key.yaml:/app/metagpt/config/key.yaml \
|
||||
-v /opt/metagpt/workspace:/app/metagpt/workspace \
|
||||
metagpt/metagpt:latest
|
||||
|
||||
docker exec -it metagpt /bin/bash
|
||||
$ python3 startup.py "Write a cli snake game"
|
||||
```
|
||||
|
||||
The command `docker run ...` do the following things:
|
||||
|
||||
- Run in privileged mode to have permission to run the browser
|
||||
- Map host configure file `/opt/metagpt/config/key.yaml` to container `/app/metagpt/config/key.yaml`
|
||||
- Map host directory `/opt/metagpt/workspace` to container `/app/metagpt/workspace`
|
||||
- Execute the demo command `python3 startup.py "Write a cli snake game"`
|
||||
|
||||
### Build image by yourself
|
||||
|
||||
```bash
|
||||
# You can also build metagpt image by yourself.
|
||||
git clone https://github.com/geekan/MetaGPT.git
|
||||
cd MetaGPT && docker build -t metagpt:custom .
|
||||
```
|
||||
44
docs/install/docker_install_cn.md
Normal file
44
docs/install/docker_install_cn.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
## Docker安装
|
||||
|
||||
### 使用MetaGPT镜像
|
||||
|
||||
```bash
|
||||
# 步骤1: 下载metagpt官方镜像并准备好config.yaml
|
||||
docker pull metagpt/metagpt:latest
|
||||
mkdir -p /opt/metagpt/{config,workspace}
|
||||
docker run --rm metagpt/metagpt:latest cat /app/metagpt/config/config.yaml > /opt/metagpt/config/key.yaml
|
||||
vim /opt/metagpt/config/key.yaml # 修改配置文件
|
||||
|
||||
# 步骤2: 使用容器运行metagpt演示
|
||||
docker run --rm \
|
||||
--privileged \
|
||||
-v /opt/metagpt/config/key.yaml:/app/metagpt/config/key.yaml \
|
||||
-v /opt/metagpt/workspace:/app/metagpt/workspace \
|
||||
metagpt/metagpt:latest \
|
||||
python startup.py "Write a cli snake game"
|
||||
|
||||
# 您也可以启动一个容器并在其中执行命令
|
||||
docker run --name metagpt -d \
|
||||
--privileged \
|
||||
-v /opt/metagpt/config/key.yaml:/app/metagpt/config/key.yaml \
|
||||
-v /opt/metagpt/workspace:/app/metagpt/workspace \
|
||||
metagpt/metagpt:latest
|
||||
|
||||
docker exec -it metagpt /bin/bash
|
||||
$ python startup.py "Write a cli snake game"
|
||||
```
|
||||
|
||||
`docker run ...`做了以下事情:
|
||||
|
||||
- 以特权模式运行,有权限运行浏览器
|
||||
- 将主机文件 `/opt/metagpt/config/key.yaml` 映射到容器文件 `/app/metagpt/config/key.yaml`
|
||||
- 将主机目录 `/opt/metagpt/workspace` 映射到容器目录 `/app/metagpt/workspace`
|
||||
- 执行示例命令 `python startup.py "Write a cli snake game"`
|
||||
|
||||
### 自己构建镜像
|
||||
|
||||
```bash
|
||||
# 您也可以自己构建metagpt镜像
|
||||
git clone https://github.com/geekan/MetaGPT.git
|
||||
cd MetaGPT && docker build -t metagpt:custom .
|
||||
```
|
||||
67
docs/tutorial/usage.md
Normal file
67
docs/tutorial/usage.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
## MetaGPT Usage
|
||||
|
||||
### Configuration
|
||||
|
||||
- Configure your `OPENAI_API_KEY` in any of `config/key.yaml / config/config.yaml / env`
|
||||
- Priority order: `config/key.yaml > config/config.yaml > env`
|
||||
|
||||
```bash
|
||||
# Copy the configuration file and make the necessary modifications.
|
||||
cp config/config.yaml config/key.yaml
|
||||
```
|
||||
|
||||
| Variable Name | config/key.yaml | env |
|
||||
| ------------------------------------------ | ----------------------------------------- | ----------------------------------------------- |
|
||||
| OPENAI_API_KEY # Replace with your own key | OPENAI_API_KEY: "sk-..." | export OPENAI_API_KEY="sk-..." |
|
||||
| OPENAI_API_BASE # Optional | OPENAI_API_BASE: "https://<YOUR_SITE>/v1" | export OPENAI_API_BASE="https://<YOUR_SITE>/v1" |
|
||||
|
||||
### Initiating a startup
|
||||
|
||||
```shell
|
||||
# Run the script
|
||||
python startup.py "Write a cli snake game"
|
||||
# Do not hire an engineer to implement the project
|
||||
python startup.py "Write a cli snake game" --implement False
|
||||
# Hire an engineer and perform code reviews
|
||||
python startup.py "Write a cli snake game" --code_review True
|
||||
```
|
||||
|
||||
After running the script, you can find your new project in the `workspace/` directory.
|
||||
|
||||
### Preference of Platform or Tool
|
||||
|
||||
You can tell which platform or tool you want to use when stating your requirements.
|
||||
|
||||
```shell
|
||||
python startup.py "Write a cli snake game based on pygame"
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
NAME
|
||||
startup.py - We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities.
|
||||
|
||||
SYNOPSIS
|
||||
startup.py IDEA <flags>
|
||||
|
||||
DESCRIPTION
|
||||
We are a software startup comprised of AI. By investing in us, you are empowering a future filled with limitless possibilities.
|
||||
|
||||
POSITIONAL ARGUMENTS
|
||||
IDEA
|
||||
Type: str
|
||||
Your innovative idea, such as "Creating a snake game."
|
||||
|
||||
FLAGS
|
||||
--investment=INVESTMENT
|
||||
Type: float
|
||||
Default: 3.0
|
||||
As an investor, you have the opportunity to contribute a certain dollar amount to this AI company.
|
||||
--n_round=N_ROUND
|
||||
Type: int
|
||||
Default: 5
|
||||
|
||||
NOTES
|
||||
You can also use flags syntax for POSITIONAL ARGUMENTS
|
||||
```
|
||||
63
docs/tutorial/usage_cn.md
Normal file
63
docs/tutorial/usage_cn.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
## MetaGPT 使用
|
||||
|
||||
### 配置
|
||||
|
||||
- 在 `config/key.yaml / config/config.yaml / env` 中配置您的 `OPENAI_API_KEY`
|
||||
- 优先级顺序:`config/key.yaml > config/config.yaml > env`
|
||||
|
||||
```bash
|
||||
# 复制配置文件并进行必要的修改
|
||||
cp config/config.yaml config/key.yaml
|
||||
```
|
||||
|
||||
| 变量名 | config/key.yaml | env |
|
||||
| ----------------------------------- | ----------------------------------------- | ----------------------------------------------- |
|
||||
| OPENAI_API_KEY # 用您自己的密钥替换 | OPENAI_API_KEY: "sk-..." | export OPENAI_API_KEY="sk-..." |
|
||||
| OPENAI_API_BASE # 可选 | OPENAI_API_BASE: "https://<YOUR_SITE>/v1" | export OPENAI_API_BASE="https://<YOUR_SITE>/v1" |
|
||||
|
||||
### 示例:启动一个创业公司
|
||||
|
||||
```shell
|
||||
python startup.py "写一个命令行贪吃蛇"
|
||||
# 开启code review模式会花费更多的金钱, 但是会提升代码质量和成功率
|
||||
python startup.py "写一个命令行贪吃蛇" --code_review True
|
||||
```
|
||||
|
||||
运行脚本后,您可以在 `workspace/` 目录中找到您的新项目。
|
||||
|
||||
### 平台或工具的倾向性
|
||||
可以在阐述需求时说明想要使用的平台或工具。
|
||||
例如:
|
||||
```shell
|
||||
python startup.py "写一个基于pygame的命令行贪吃蛇"
|
||||
```
|
||||
|
||||
### 使用
|
||||
|
||||
```
|
||||
名称
|
||||
startup.py - 我们是一家AI软件创业公司。通过投资我们,您将赋能一个充满无限可能的未来。
|
||||
|
||||
概要
|
||||
startup.py IDEA <flags>
|
||||
|
||||
描述
|
||||
我们是一家AI软件创业公司。通过投资我们,您将赋能一个充满无限可能的未来。
|
||||
|
||||
位置参数
|
||||
IDEA
|
||||
类型: str
|
||||
您的创新想法,例如"写一个命令行贪吃蛇。"
|
||||
|
||||
标志
|
||||
--investment=INVESTMENT
|
||||
类型: float
|
||||
默认值: 3.0
|
||||
作为投资者,您有机会向这家AI公司投入一定的美元金额。
|
||||
--n_round=N_ROUND
|
||||
类型: int
|
||||
默认值: 5
|
||||
|
||||
备注
|
||||
您也可以用`标志`的语法,来处理`位置参数`
|
||||
```
|
||||
158
examples/build_customized_multi_agents.py
Normal file
158
examples/build_customized_multi_agents.py
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
'''
|
||||
Filename: MetaGPT/examples/build_customized_multi_agents.py
|
||||
Created Date: Wednesday, November 15th 2023, 7:12:39 pm
|
||||
Author: garylin2099
|
||||
'''
|
||||
import re
|
||||
import asyncio
|
||||
import fire
|
||||
|
||||
from metagpt.llm import LLM
|
||||
from metagpt.actions import Action, BossRequirement
|
||||
from metagpt.roles import Role
|
||||
from metagpt.team import Team
|
||||
from metagpt.schema import Message
|
||||
from metagpt.logs import logger
|
||||
|
||||
def parse_code(rsp):
|
||||
pattern = r'```python(.*)```'
|
||||
match = re.search(pattern, rsp, re.DOTALL)
|
||||
code_text = match.group(1) if match else rsp
|
||||
return code_text
|
||||
|
||||
class SimpleWriteCode(Action):
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
Write a python function that can {instruction}.
|
||||
Return ```python your_code_here ``` with NO other texts,
|
||||
your code:
|
||||
"""
|
||||
|
||||
def __init__(self, name: str = "SimpleWriteCode", context=None, llm: LLM = None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
async def run(self, instruction: str):
|
||||
|
||||
prompt = self.PROMPT_TEMPLATE.format(instruction=instruction)
|
||||
|
||||
rsp = await self._aask(prompt)
|
||||
|
||||
code_text = parse_code(rsp)
|
||||
|
||||
return code_text
|
||||
|
||||
|
||||
class SimpleCoder(Role):
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "Alice",
|
||||
profile: str = "SimpleCoder",
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(name, profile, **kwargs)
|
||||
self._watch([BossRequirement])
|
||||
self._init_actions([SimpleWriteCode])
|
||||
|
||||
|
||||
class SimpleWriteTest(Action):
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
Context: {context}
|
||||
Write {k} unit tests using pytest for the given function, assuming you have imported it.
|
||||
Return ```python your_code_here ``` with NO other texts,
|
||||
your code:
|
||||
"""
|
||||
|
||||
def __init__(self, name: str = "SimpleWriteTest", context=None, llm: LLM = None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
async def run(self, context: str, k: int = 3):
|
||||
|
||||
prompt = self.PROMPT_TEMPLATE.format(context=context, k=k)
|
||||
|
||||
rsp = await self._aask(prompt)
|
||||
|
||||
code_text = parse_code(rsp)
|
||||
|
||||
return code_text
|
||||
|
||||
|
||||
class SimpleTester(Role):
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "Bob",
|
||||
profile: str = "SimpleTester",
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(name, profile, **kwargs)
|
||||
self._init_actions([SimpleWriteTest])
|
||||
# self._watch([SimpleWriteCode])
|
||||
self._watch([SimpleWriteCode, SimpleWriteReview]) # feel free to try this too
|
||||
|
||||
async def _act(self) -> Message:
|
||||
logger.info(f"{self._setting}: ready to {self._rc.todo}")
|
||||
todo = self._rc.todo
|
||||
|
||||
# context = self.get_memories(k=1)[0].content # use the most recent memory as context
|
||||
context = self.get_memories() # use all memories as context
|
||||
|
||||
code_text = await todo.run(context, k=5) # specify arguments
|
||||
msg = Message(content=code_text, role=self.profile, cause_by=type(todo))
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
class SimpleWriteReview(Action):
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
Context: {context}
|
||||
Review the test cases and provide one critical comments:
|
||||
"""
|
||||
|
||||
def __init__(self, name: str = "SimpleWriteReview", context=None, llm: LLM = None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
async def run(self, context: str):
|
||||
|
||||
prompt = self.PROMPT_TEMPLATE.format(context=context)
|
||||
|
||||
rsp = await self._aask(prompt)
|
||||
|
||||
return rsp
|
||||
|
||||
|
||||
class SimpleReviewer(Role):
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "Charlie",
|
||||
profile: str = "SimpleReviewer",
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(name, profile, **kwargs)
|
||||
self._init_actions([SimpleWriteReview])
|
||||
self._watch([SimpleWriteTest])
|
||||
|
||||
|
||||
async def main(
|
||||
idea: str = "write a function that calculates the product of a list",
|
||||
investment: float = 3.0,
|
||||
n_round: int = 5,
|
||||
add_human: bool = False,
|
||||
):
|
||||
logger.info(idea)
|
||||
|
||||
team = Team()
|
||||
team.hire(
|
||||
[
|
||||
SimpleCoder(),
|
||||
SimpleTester(),
|
||||
SimpleReviewer(is_human=add_human),
|
||||
]
|
||||
)
|
||||
|
||||
team.invest(investment=investment)
|
||||
team.start_project(idea)
|
||||
await team.run(n_round=n_round)
|
||||
|
||||
if __name__ == '__main__':
|
||||
fire.Fire(main)
|
||||
30
metagpt/provider/constant.py
Normal file
30
metagpt/provider/constant.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# function in tools, https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools
|
||||
# Reference: https://github.com/KillianLucas/open-interpreter/blob/v0.1.14/interpreter/llm/setup_openai_coding_llm.py
|
||||
GENERAL_FUNCTION_SCHEMA = {
|
||||
"name": "execute",
|
||||
"description": "Executes code on the user's machine, **in the users local environment**, and returns the output",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "The programming language (required parameter to the `execute` function)",
|
||||
"enum": [
|
||||
"python",
|
||||
"R",
|
||||
"shell",
|
||||
"applescript",
|
||||
"javascript",
|
||||
"html",
|
||||
"powershell",
|
||||
],
|
||||
},
|
||||
"code": {"type": "string", "description": "The code to execute (required)"},
|
||||
},
|
||||
"required": ["language", "code"],
|
||||
},
|
||||
}
|
||||
|
||||
# tool_choice value for general_function_schema
|
||||
# https://platform.openai.com/docs/api-reference/chat/create#chat-create-tool_choice
|
||||
GENERAL_TOOL_CHOICE = {"type": "function", "function": {"name": "execute"}}
|
||||
62
metagpt/provider/general_api_requestor.py
Normal file
62
metagpt/provider/general_api_requestor.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc : General Async API for http-based LLM model
|
||||
|
||||
from typing import AsyncGenerator, Tuple, Union, Optional, Literal
|
||||
import aiohttp
|
||||
import asyncio
|
||||
|
||||
from openai.api_requestor import APIRequestor
|
||||
|
||||
from metagpt.logs import logger
|
||||
|
||||
|
||||
class GeneralAPIRequestor(APIRequestor):
|
||||
"""
|
||||
usage
|
||||
# full_url = "{api_base}{url}"
|
||||
requester = GeneralAPIRequestor(api_base=api_base)
|
||||
result, _, api_key = await requester.arequest(
|
||||
method=method,
|
||||
url=url,
|
||||
headers=headers,
|
||||
stream=stream,
|
||||
params=kwargs,
|
||||
request_timeout=120
|
||||
)
|
||||
"""
|
||||
|
||||
def _interpret_response_line(
|
||||
self, rbody: str, rcode: int, rheaders, stream: bool
|
||||
) -> str:
|
||||
# just do nothing to meet the APIRequestor process and return the raw data
|
||||
# due to the openai sdk will convert the data into OpenAIResponse which we don't need in general cases.
|
||||
|
||||
return rbody
|
||||
|
||||
async def _interpret_async_response(
|
||||
self, result: aiohttp.ClientResponse, stream: bool
|
||||
) -> Tuple[Union[str, AsyncGenerator[str, None]], bool]:
|
||||
if stream and "text/event-stream" in result.headers.get("Content-Type", ""):
|
||||
return (
|
||||
self._interpret_response_line(
|
||||
line, result.status, result.headers, stream=True
|
||||
)
|
||||
async for line in result.content
|
||||
), True
|
||||
else:
|
||||
try:
|
||||
await result.read()
|
||||
except (aiohttp.ServerTimeoutError, asyncio.TimeoutError) as e:
|
||||
raise TimeoutError("Request timed out") from e
|
||||
except aiohttp.ClientError as exp:
|
||||
logger.warning(f"response: {result.content}, exp: {exp}")
|
||||
return (
|
||||
self._interpret_response_line(
|
||||
await result.read(), # let the caller to decode the msg
|
||||
result.status,
|
||||
result.headers,
|
||||
stream=False,
|
||||
),
|
||||
False,
|
||||
)
|
||||
35
metagpt/provider/human_provider.py
Normal file
35
metagpt/provider/human_provider.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
'''
|
||||
Filename: MetaGPT/metagpt/provider/human_provider.py
|
||||
Created Date: Wednesday, November 8th 2023, 11:55:46 pm
|
||||
Author: garylin2099
|
||||
'''
|
||||
from typing import Optional
|
||||
from metagpt.provider.base_gpt_api import BaseGPTAPI
|
||||
from metagpt.logs import logger
|
||||
|
||||
class HumanProvider(BaseGPTAPI):
|
||||
"""Humans provide themselves as a 'model', which actually takes in human input as its response.
|
||||
This enables replacing LLM anywhere in the framework with a human, thus introducing human interaction
|
||||
"""
|
||||
|
||||
def ask(self, msg: str) -> str:
|
||||
logger.info("It's your turn, please type in your response. You may also refer to the context below")
|
||||
rsp = input(msg)
|
||||
if rsp in ["exit", "quit"]:
|
||||
exit()
|
||||
return rsp
|
||||
|
||||
async def aask(self, msg: str, system_msgs: Optional[list[str]] = None) -> str:
|
||||
return self.ask(msg)
|
||||
|
||||
def completion(self, messages: list[dict]):
|
||||
"""dummy implementation of abstract method in base"""
|
||||
return []
|
||||
|
||||
async def acompletion(self, messages: list[dict]):
|
||||
"""dummy implementation of abstract method in base"""
|
||||
return []
|
||||
|
||||
async def acompletion_text(self, messages: list[dict], stream=False) -> str:
|
||||
"""dummy implementation of abstract method in base"""
|
||||
return []
|
||||
3
metagpt/provider/zhipuai/__init__.py
Normal file
3
metagpt/provider/zhipuai/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc :
|
||||
78
metagpt/provider/zhipuai/async_sse_client.py
Normal file
78
metagpt/provider/zhipuai/async_sse_client.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc : async_sse_client to make keep the use of Event to access response
|
||||
# refs to `https://github.com/zhipuai/zhipuai-sdk-python/blob/main/zhipuai/utils/sse_client.py`
|
||||
|
||||
from zhipuai.utils.sse_client import SSEClient, Event, _FIELD_SEPARATOR
|
||||
|
||||
|
||||
class AsyncSSEClient(SSEClient):
|
||||
|
||||
async def _aread(self):
|
||||
data = b""
|
||||
async for chunk in self._event_source:
|
||||
for line in chunk.splitlines(True):
|
||||
data += line
|
||||
if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
|
||||
yield data
|
||||
data = b""
|
||||
if data:
|
||||
yield data
|
||||
|
||||
async def async_events(self):
|
||||
async for chunk in self._aread():
|
||||
event = Event()
|
||||
# Split before decoding so splitlines() only uses \r and \n
|
||||
for line in chunk.splitlines():
|
||||
# Decode the line.
|
||||
line = line.decode(self._char_enc)
|
||||
|
||||
# Lines starting with a separator are comments and are to be
|
||||
# ignored.
|
||||
if not line.strip() or line.startswith(_FIELD_SEPARATOR):
|
||||
continue
|
||||
|
||||
data = line.split(_FIELD_SEPARATOR, 1)
|
||||
field = data[0]
|
||||
|
||||
# Ignore unknown fields.
|
||||
if field not in event.__dict__:
|
||||
self._logger.debug(
|
||||
"Saw invalid field %s while parsing " "Server Side Event", field
|
||||
)
|
||||
continue
|
||||
|
||||
if len(data) > 1:
|
||||
# From the spec:
|
||||
# "If value starts with a single U+0020 SPACE character,
|
||||
# remove it from value."
|
||||
if data[1].startswith(" "):
|
||||
value = data[1][1:]
|
||||
else:
|
||||
value = data[1]
|
||||
else:
|
||||
# If no value is present after the separator,
|
||||
# assume an empty value.
|
||||
value = ""
|
||||
|
||||
# The data field may come over multiple lines and their values
|
||||
# are concatenated with each other.
|
||||
if field == "data":
|
||||
event.__dict__[field] += value + "\n"
|
||||
else:
|
||||
event.__dict__[field] = value
|
||||
|
||||
# Events with no data are not dispatched.
|
||||
if not event.data:
|
||||
continue
|
||||
|
||||
# If the data field ends with a newline, remove it.
|
||||
if event.data.endswith("\n"):
|
||||
event.data = event.data[0:-1]
|
||||
|
||||
# Empty event names default to 'message'
|
||||
event.event = event.event or "message"
|
||||
|
||||
# Dispatch the event
|
||||
self._logger.debug("Dispatching %s...", event)
|
||||
yield event
|
||||
79
metagpt/provider/zhipuai/zhipu_model_api.py
Normal file
79
metagpt/provider/zhipuai/zhipu_model_api.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc : zhipu model api to support sync & async for invoke & sse_invoke
|
||||
|
||||
import zhipuai
|
||||
from zhipuai.model_api.api import ModelAPI, InvokeType
|
||||
from zhipuai.utils.http_client import headers as zhipuai_default_headers
|
||||
|
||||
from metagpt.provider.zhipuai.async_sse_client import AsyncSSEClient
|
||||
from metagpt.provider.general_api_requestor import GeneralAPIRequestor
|
||||
|
||||
|
||||
class ZhiPuModelAPI(ModelAPI):
|
||||
|
||||
@classmethod
|
||||
def get_header(cls) -> dict:
|
||||
token = cls._generate_token()
|
||||
zhipuai_default_headers.update({"Authorization": token})
|
||||
return zhipuai_default_headers
|
||||
|
||||
@classmethod
|
||||
def get_sse_header(cls) -> dict:
|
||||
token = cls._generate_token()
|
||||
headers = {
|
||||
"Authorization": token
|
||||
}
|
||||
return headers
|
||||
|
||||
@classmethod
|
||||
def split_zhipu_api_url(cls, invoke_type: InvokeType, kwargs):
|
||||
# use this method to prevent zhipu api upgrading to different version.
|
||||
# and follow the GeneralAPIRequestor implemented based on openai sdk
|
||||
zhipu_api_url = cls._build_api_url(kwargs, invoke_type)
|
||||
"""
|
||||
example:
|
||||
zhipu_api_url: https://open.bigmodel.cn/api/paas/v3/model-api/{model}/{invoke_method}
|
||||
"""
|
||||
arr = zhipu_api_url.split("/api/")
|
||||
# ("https://open.bigmodel.cn/api/" , "/paas/v3/model-api/chatglm_turbo/invoke")
|
||||
return f"{arr[0]}/api", f"/{arr[1]}"
|
||||
|
||||
@classmethod
|
||||
async def arequest(cls, invoke_type: InvokeType, stream: bool, method: str, headers: dict, kwargs):
|
||||
# TODO to make the async request to be more generic for models in http mode.
|
||||
assert method in ["post", "get"]
|
||||
|
||||
api_base, url = cls.split_zhipu_api_url(invoke_type, kwargs)
|
||||
requester = GeneralAPIRequestor(api_base=api_base)
|
||||
result, _, api_key = await requester.arequest(
|
||||
method=method,
|
||||
url=url,
|
||||
headers=headers,
|
||||
stream=stream,
|
||||
params=kwargs,
|
||||
request_timeout=zhipuai.api_timeout_seconds
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
async def ainvoke(cls, **kwargs) -> dict:
|
||||
""" async invoke different from raw method `async_invoke` which get the final result by task_id"""
|
||||
headers = cls.get_header()
|
||||
resp = await cls.arequest(invoke_type=InvokeType.SYNC,
|
||||
stream=False,
|
||||
method="post",
|
||||
headers=headers,
|
||||
kwargs=kwargs)
|
||||
return resp
|
||||
|
||||
@classmethod
|
||||
async def asse_invoke(cls, **kwargs) -> AsyncSSEClient:
|
||||
""" async sse_invoke """
|
||||
headers = cls.get_sse_header()
|
||||
return AsyncSSEClient(await cls.arequest(invoke_type=InvokeType.SSE,
|
||||
stream=True,
|
||||
method="post",
|
||||
headers=headers,
|
||||
kwargs=kwargs))
|
||||
139
metagpt/provider/zhipuai_api.py
Normal file
139
metagpt/provider/zhipuai_api.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc : zhipuai LLM from https://open.bigmodel.cn/dev/api#sdk
|
||||
|
||||
from enum import Enum
|
||||
import json
|
||||
from tenacity import (
|
||||
after_log,
|
||||
retry,
|
||||
retry_if_exception_type,
|
||||
stop_after_attempt,
|
||||
wait_fixed,
|
||||
)
|
||||
from requests import ConnectionError
|
||||
|
||||
import openai
|
||||
import zhipuai
|
||||
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.logs import logger
|
||||
from metagpt.provider.base_gpt_api import BaseGPTAPI
|
||||
from metagpt.provider.openai_api import CostManager, log_and_reraise
|
||||
from metagpt.provider.zhipuai.zhipu_model_api import ZhiPuModelAPI
|
||||
|
||||
|
||||
class ZhiPuEvent(Enum):
|
||||
ADD = "add"
|
||||
ERROR = "error"
|
||||
INTERRUPTED = "interrupted"
|
||||
FINISH = "finish"
|
||||
|
||||
|
||||
class ZhiPuAIGPTAPI(BaseGPTAPI):
|
||||
"""
|
||||
Refs to `https://open.bigmodel.cn/dev/api#chatglm_turbo`
|
||||
From now, there is only one model named `chatglm_turbo`
|
||||
"""
|
||||
|
||||
use_system_prompt: bool = False # zhipuai has no system prompt when use api
|
||||
|
||||
def __init__(self):
|
||||
self.__init_zhipuai(CONFIG)
|
||||
self.llm = ZhiPuModelAPI
|
||||
self.model = "chatglm_turbo" # so far only one model, just use it
|
||||
self._cost_manager = CostManager()
|
||||
|
||||
def __init_zhipuai(self, config: CONFIG):
|
||||
assert config.zhipuai_api_key
|
||||
zhipuai.api_key = config.zhipuai_api_key
|
||||
openai.api_key = zhipuai.api_key # due to use openai sdk, set the api_key but it will't be used.
|
||||
|
||||
def _const_kwargs(self, messages: list[dict]) -> dict:
|
||||
kwargs = {
|
||||
"model": self.model,
|
||||
"prompt": messages,
|
||||
"temperature": 0.3
|
||||
}
|
||||
return kwargs
|
||||
|
||||
def _update_costs(self, usage: dict):
|
||||
""" update each request's token cost """
|
||||
if CONFIG.calc_usage:
|
||||
try:
|
||||
prompt_tokens = int(usage.get("prompt_tokens", 0))
|
||||
completion_tokens = int(usage.get("completion_tokens", 0))
|
||||
self._cost_manager.update_cost(prompt_tokens, completion_tokens, self.model)
|
||||
except Exception as e:
|
||||
logger.error("zhipuai updats costs failed!", e)
|
||||
|
||||
def get_choice_text(self, resp: dict) -> str:
|
||||
""" get the first text of choice from llm response """
|
||||
assist_msg = resp.get("data", {}).get("choices", [{"role": "error"}])[-1]
|
||||
assert assist_msg["role"] == "assistant"
|
||||
return assist_msg.get("content")
|
||||
|
||||
def completion(self, messages: list[dict]) -> dict:
|
||||
resp = self.llm.invoke(**self._const_kwargs(messages))
|
||||
usage = resp.get("data").get("usage")
|
||||
self._update_costs(usage)
|
||||
return resp
|
||||
|
||||
async def _achat_completion(self, messages: list[dict]) -> dict:
|
||||
resp = await self.llm.ainvoke(**self._const_kwargs(messages))
|
||||
usage = resp.get("data").get("usage")
|
||||
self._update_costs(usage)
|
||||
return resp
|
||||
|
||||
async def acompletion(self, messages: list[dict]) -> dict:
|
||||
return await self._achat_completion(messages)
|
||||
|
||||
async def _achat_completion_stream(self, messages: list[dict]) -> str:
|
||||
response = await self.llm.asse_invoke(**self._const_kwargs(messages))
|
||||
collected_content = []
|
||||
usage = {}
|
||||
async for event in response.async_events():
|
||||
if event.event == ZhiPuEvent.ADD.value:
|
||||
content = event.data
|
||||
collected_content.append(content)
|
||||
print(content, end="")
|
||||
elif event.event == ZhiPuEvent.ERROR.value or event.event == ZhiPuEvent.INTERRUPTED.value:
|
||||
content = event.data
|
||||
logger.error(f"event error: {content}", end="")
|
||||
collected_content.append([content])
|
||||
elif event.event == ZhiPuEvent.FINISH.value:
|
||||
"""
|
||||
event.meta
|
||||
{
|
||||
"task_status":"SUCCESS",
|
||||
"usage":{
|
||||
"completion_tokens":351,
|
||||
"prompt_tokens":595,
|
||||
"total_tokens":946
|
||||
},
|
||||
"task_id":"xx",
|
||||
"request_id":"xxx"
|
||||
}
|
||||
"""
|
||||
meta = json.loads(event.meta)
|
||||
usage = meta.get("usage")
|
||||
else:
|
||||
print(f"zhipuapi else event: {event.data}", end="")
|
||||
|
||||
self._update_costs(usage)
|
||||
full_content = "".join(collected_content)
|
||||
return full_content
|
||||
|
||||
@retry(
|
||||
stop=stop_after_attempt(3),
|
||||
wait=wait_fixed(1),
|
||||
after=after_log(logger, logger.level("WARNING").name),
|
||||
retry=retry_if_exception_type(ConnectionError),
|
||||
retry_error_callback=log_and_reraise
|
||||
)
|
||||
async def acompletion_text(self, messages: list[dict], stream=False) -> str:
|
||||
""" response in async with stream or non-stream mode """
|
||||
if stream:
|
||||
return await self._achat_completion_stream(messages)
|
||||
resp = await self._achat_completion(messages)
|
||||
return self.get_choice_text(resp)
|
||||
62
metagpt/team.py
Normal file
62
metagpt/team.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2023/5/12 00:30
|
||||
@Author : alexanderwu
|
||||
@File : software_company.py
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from metagpt.actions import BossRequirement
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.environment import Environment
|
||||
from metagpt.logs import logger
|
||||
from metagpt.roles import Role
|
||||
from metagpt.schema import Message
|
||||
from metagpt.utils.common import NoMoneyException
|
||||
|
||||
|
||||
class Team(BaseModel):
|
||||
"""
|
||||
Team: Possesses one or more roles (agents), SOP (Standard Operating Procedures), and a platform for instant messaging,
|
||||
dedicated to perform any multi-agent activity, such as collaboratively writing executable code.
|
||||
"""
|
||||
environment: Environment = Field(default_factory=Environment)
|
||||
investment: float = Field(default=10.0)
|
||||
idea: str = Field(default="")
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
def hire(self, roles: list[Role]):
|
||||
"""Hire roles to cooperate"""
|
||||
self.environment.add_roles(roles)
|
||||
|
||||
def invest(self, investment: float):
|
||||
"""Invest company. raise NoMoneyException when exceed max_budget."""
|
||||
self.investment = investment
|
||||
CONFIG.max_budget = investment
|
||||
logger.info(f'Investment: ${investment}.')
|
||||
|
||||
def _check_balance(self):
|
||||
if CONFIG.total_cost > CONFIG.max_budget:
|
||||
raise NoMoneyException(CONFIG.total_cost, f'Insufficient funds: {CONFIG.max_budget}')
|
||||
|
||||
def start_project(self, idea, send_to: str = ""):
|
||||
"""Start a project from publishing boss requirement."""
|
||||
self.idea = idea
|
||||
self.environment.publish_message(Message(role="Human", content=idea, cause_by=BossRequirement, send_to=send_to))
|
||||
|
||||
def _save(self):
|
||||
logger.info(self.json())
|
||||
|
||||
async def run(self, n_round=3):
|
||||
"""Run company until target round or no money"""
|
||||
while n_round > 0:
|
||||
# self._save()
|
||||
n_round -= 1
|
||||
logger.debug(f"{n_round=}")
|
||||
self._check_balance()
|
||||
await self.environment.run()
|
||||
return self.environment.history
|
||||
|
||||
80
tests/metagpt/provider/test_openai.py
Normal file
80
tests/metagpt/provider/test_openai.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import pytest
|
||||
|
||||
from metagpt.provider.openai_api import OpenAIGPTAPI
|
||||
from metagpt.schema import UserMessage
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aask_code():
|
||||
llm = OpenAIGPTAPI()
|
||||
msg = [{"role": "user", "content": "Write a python hello world code."}]
|
||||
rsp = await llm.aask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"}
|
||||
assert "language" in rsp
|
||||
assert "code" in rsp
|
||||
assert len(rsp["code"]) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aask_code_str():
|
||||
llm = OpenAIGPTAPI()
|
||||
msg = "Write a python hello world code."
|
||||
rsp = await llm.aask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"}
|
||||
assert "language" in rsp
|
||||
assert "code" in rsp
|
||||
assert len(rsp["code"]) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aask_code_Message():
|
||||
llm = OpenAIGPTAPI()
|
||||
msg = UserMessage("Write a python hello world code.")
|
||||
rsp = await llm.aask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"}
|
||||
assert "language" in rsp
|
||||
assert "code" in rsp
|
||||
assert len(rsp["code"]) > 0
|
||||
|
||||
|
||||
def test_ask_code():
|
||||
llm = OpenAIGPTAPI()
|
||||
msg = [{"role": "user", "content": "Write a python hello world code."}]
|
||||
rsp = llm.ask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"}
|
||||
assert "language" in rsp
|
||||
assert "code" in rsp
|
||||
assert len(rsp["code"]) > 0
|
||||
|
||||
|
||||
def test_ask_code_str():
|
||||
llm = OpenAIGPTAPI()
|
||||
msg = "Write a python hello world code."
|
||||
rsp = llm.ask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"}
|
||||
assert "language" in rsp
|
||||
assert "code" in rsp
|
||||
assert len(rsp["code"]) > 0
|
||||
|
||||
|
||||
def test_ask_code_Message():
|
||||
llm = OpenAIGPTAPI()
|
||||
msg = UserMessage("Write a python hello world code.")
|
||||
rsp = llm.ask_code(msg) # -> {'language': 'python', 'code': "print('Hello, World!')"}
|
||||
assert "language" in rsp
|
||||
assert "code" in rsp
|
||||
assert len(rsp["code"]) > 0
|
||||
|
||||
|
||||
def test_ask_code_list_Message():
|
||||
llm = OpenAIGPTAPI()
|
||||
msg = [UserMessage("a=[1,2,5,10,-10]"), UserMessage("写出求a中最大值的代码python")]
|
||||
rsp = llm.ask_code(msg) # -> {'language': 'python', 'code': 'max_value = max(a)\nmax_value'}
|
||||
assert "language" in rsp
|
||||
assert "code" in rsp
|
||||
assert len(rsp["code"]) > 0
|
||||
|
||||
|
||||
def test_ask_code_list_str():
|
||||
llm = OpenAIGPTAPI()
|
||||
msg = ["a=[1,2,5,10,-10]", "写出求a中最大值的代码python"]
|
||||
rsp = llm.ask_code(msg) # -> {'language': 'python', 'code': 'max_value = max(a)\nmax_value'}
|
||||
print(rsp)
|
||||
assert "language" in rsp
|
||||
assert "code" in rsp
|
||||
assert len(rsp["code"]) > 0
|
||||
47
tests/metagpt/provider/test_zhipuai_api.py
Normal file
47
tests/metagpt/provider/test_zhipuai_api.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc : the unittest of ZhiPuAIGPTAPI
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.provider.zhipuai_api import ZhiPuAIGPTAPI
|
||||
|
||||
|
||||
default_resp = {
|
||||
"code": 200,
|
||||
"data": {
|
||||
"choices": [
|
||||
{"role": "assistant", "content": "I'm chatglm-turbo"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "who are you"}
|
||||
]
|
||||
|
||||
|
||||
def mock_llm_ask(self, messages: list[dict]) -> dict:
|
||||
return default_resp
|
||||
|
||||
|
||||
def test_zhipuai_completion(mocker):
|
||||
mocker.patch("metagpt.provider.zhipuai_api.ZhiPuAIGPTAPI.completion", mock_llm_ask)
|
||||
|
||||
resp = ZhiPuAIGPTAPI().completion(messages)
|
||||
assert resp["code"] == 200
|
||||
assert "chatglm-turbo" in resp["data"]["choices"][0]["content"]
|
||||
|
||||
|
||||
async def mock_llm_aask(self, messgaes: list[dict], stream: bool = False) -> dict:
|
||||
return default_resp
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_zhipuai_acompletion(mocker):
|
||||
mocker.patch("metagpt.provider.zhipuai_api.ZhiPuAIGPTAPI.acompletion_text", mock_llm_aask)
|
||||
|
||||
resp = await ZhiPuAIGPTAPI().acompletion_text(messages, stream=False)
|
||||
|
||||
assert resp["code"] == 200
|
||||
assert "chatglm-turbo" in resp["data"]["choices"][0]["content"]
|
||||
Loading…
Add table
Add a link
Reference in a new issue