init
This commit is contained in:
523
AlgoriAgent/src/agentscope.egg-info/PKG-INFO
Normal file
523
AlgoriAgent/src/agentscope.egg-info/PKG-INFO
Normal file
@@ -0,0 +1,523 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: agentscope
|
||||
Version: 0.0.6a3
|
||||
Summary: AgentScope: A Flexible yet Robust Multi-Agent Platform.
|
||||
Home-page: https://github.com/modelscope/agentscope
|
||||
Download-URL: https://github.com/modelscope/agentscope/archive/v0.0.6alpha3.tar.gz
|
||||
Author: SysML team of Alibaba Tongyi Lab
|
||||
Author-email: gaodawei.gdw@alibaba-inc.com
|
||||
License: Apache License 2.0
|
||||
Keywords: deep-learning,multi agents,agents
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/markdown
|
||||
License-File: LICENSE
|
||||
Requires-Dist: networkx
|
||||
Requires-Dist: black
|
||||
Requires-Dist: docstring_parser
|
||||
Requires-Dist: pydantic
|
||||
Requires-Dist: loguru==0.6.0
|
||||
Requires-Dist: tiktoken
|
||||
Requires-Dist: Pillow
|
||||
Requires-Dist: requests
|
||||
Requires-Dist: chardet
|
||||
Requires-Dist: inputimeout
|
||||
Requires-Dist: openai>=1.3.0
|
||||
Requires-Dist: numpy
|
||||
Requires-Dist: Flask==3.0.0
|
||||
Requires-Dist: Flask-Cors==4.0.0
|
||||
Requires-Dist: Flask-SocketIO==5.3.6
|
||||
Requires-Dist: flask_sqlalchemy
|
||||
Requires-Dist: flake8
|
||||
Requires-Dist: dashscope==1.14.1
|
||||
Requires-Dist: openai>=1.3.0
|
||||
Requires-Dist: ollama>=0.1.7
|
||||
Requires-Dist: google-generativeai>=0.4.0
|
||||
Requires-Dist: zhipuai
|
||||
Requires-Dist: litellm
|
||||
Requires-Dist: psutil
|
||||
Requires-Dist: scipy
|
||||
Provides-Extra: distribute
|
||||
Requires-Dist: networkx; extra == "distribute"
|
||||
Requires-Dist: black; extra == "distribute"
|
||||
Requires-Dist: docstring_parser; extra == "distribute"
|
||||
Requires-Dist: pydantic; extra == "distribute"
|
||||
Requires-Dist: loguru==0.6.0; extra == "distribute"
|
||||
Requires-Dist: tiktoken; extra == "distribute"
|
||||
Requires-Dist: Pillow; extra == "distribute"
|
||||
Requires-Dist: requests; extra == "distribute"
|
||||
Requires-Dist: chardet; extra == "distribute"
|
||||
Requires-Dist: inputimeout; extra == "distribute"
|
||||
Requires-Dist: openai>=1.3.0; extra == "distribute"
|
||||
Requires-Dist: numpy; extra == "distribute"
|
||||
Requires-Dist: Flask==3.0.0; extra == "distribute"
|
||||
Requires-Dist: Flask-Cors==4.0.0; extra == "distribute"
|
||||
Requires-Dist: Flask-SocketIO==5.3.6; extra == "distribute"
|
||||
Requires-Dist: flask_sqlalchemy; extra == "distribute"
|
||||
Requires-Dist: flake8; extra == "distribute"
|
||||
Requires-Dist: dashscope==1.14.1; extra == "distribute"
|
||||
Requires-Dist: openai>=1.3.0; extra == "distribute"
|
||||
Requires-Dist: ollama>=0.1.7; extra == "distribute"
|
||||
Requires-Dist: google-generativeai>=0.4.0; extra == "distribute"
|
||||
Requires-Dist: zhipuai; extra == "distribute"
|
||||
Requires-Dist: litellm; extra == "distribute"
|
||||
Requires-Dist: psutil; extra == "distribute"
|
||||
Requires-Dist: scipy; extra == "distribute"
|
||||
Requires-Dist: grpcio==1.60.0; extra == "distribute"
|
||||
Requires-Dist: grpcio-tools==1.60.0; extra == "distribute"
|
||||
Requires-Dist: protobuf==4.25.0; extra == "distribute"
|
||||
Requires-Dist: expiringdict; extra == "distribute"
|
||||
Requires-Dist: dill; extra == "distribute"
|
||||
Provides-Extra: dev
|
||||
Requires-Dist: networkx; extra == "dev"
|
||||
Requires-Dist: black; extra == "dev"
|
||||
Requires-Dist: docstring_parser; extra == "dev"
|
||||
Requires-Dist: pydantic; extra == "dev"
|
||||
Requires-Dist: loguru==0.6.0; extra == "dev"
|
||||
Requires-Dist: tiktoken; extra == "dev"
|
||||
Requires-Dist: Pillow; extra == "dev"
|
||||
Requires-Dist: requests; extra == "dev"
|
||||
Requires-Dist: chardet; extra == "dev"
|
||||
Requires-Dist: inputimeout; extra == "dev"
|
||||
Requires-Dist: openai>=1.3.0; extra == "dev"
|
||||
Requires-Dist: numpy; extra == "dev"
|
||||
Requires-Dist: Flask==3.0.0; extra == "dev"
|
||||
Requires-Dist: Flask-Cors==4.0.0; extra == "dev"
|
||||
Requires-Dist: Flask-SocketIO==5.3.6; extra == "dev"
|
||||
Requires-Dist: flask_sqlalchemy; extra == "dev"
|
||||
Requires-Dist: flake8; extra == "dev"
|
||||
Requires-Dist: dashscope==1.14.1; extra == "dev"
|
||||
Requires-Dist: openai>=1.3.0; extra == "dev"
|
||||
Requires-Dist: ollama>=0.1.7; extra == "dev"
|
||||
Requires-Dist: google-generativeai>=0.4.0; extra == "dev"
|
||||
Requires-Dist: zhipuai; extra == "dev"
|
||||
Requires-Dist: litellm; extra == "dev"
|
||||
Requires-Dist: psutil; extra == "dev"
|
||||
Requires-Dist: scipy; extra == "dev"
|
||||
Requires-Dist: pytest; extra == "dev"
|
||||
Requires-Dist: pytest-cov; extra == "dev"
|
||||
Requires-Dist: pre-commit; extra == "dev"
|
||||
Provides-Extra: full
|
||||
Requires-Dist: networkx; extra == "full"
|
||||
Requires-Dist: black; extra == "full"
|
||||
Requires-Dist: docstring_parser; extra == "full"
|
||||
Requires-Dist: pydantic; extra == "full"
|
||||
Requires-Dist: loguru==0.6.0; extra == "full"
|
||||
Requires-Dist: tiktoken; extra == "full"
|
||||
Requires-Dist: Pillow; extra == "full"
|
||||
Requires-Dist: requests; extra == "full"
|
||||
Requires-Dist: chardet; extra == "full"
|
||||
Requires-Dist: inputimeout; extra == "full"
|
||||
Requires-Dist: openai>=1.3.0; extra == "full"
|
||||
Requires-Dist: numpy; extra == "full"
|
||||
Requires-Dist: Flask==3.0.0; extra == "full"
|
||||
Requires-Dist: Flask-Cors==4.0.0; extra == "full"
|
||||
Requires-Dist: Flask-SocketIO==5.3.6; extra == "full"
|
||||
Requires-Dist: flask_sqlalchemy; extra == "full"
|
||||
Requires-Dist: flake8; extra == "full"
|
||||
Requires-Dist: dashscope==1.14.1; extra == "full"
|
||||
Requires-Dist: openai>=1.3.0; extra == "full"
|
||||
Requires-Dist: ollama>=0.1.7; extra == "full"
|
||||
Requires-Dist: google-generativeai>=0.4.0; extra == "full"
|
||||
Requires-Dist: zhipuai; extra == "full"
|
||||
Requires-Dist: litellm; extra == "full"
|
||||
Requires-Dist: psutil; extra == "full"
|
||||
Requires-Dist: scipy; extra == "full"
|
||||
Requires-Dist: grpcio==1.60.0; extra == "full"
|
||||
Requires-Dist: grpcio-tools==1.60.0; extra == "full"
|
||||
Requires-Dist: protobuf==4.25.0; extra == "full"
|
||||
Requires-Dist: expiringdict; extra == "full"
|
||||
Requires-Dist: dill; extra == "full"
|
||||
Requires-Dist: docker; extra == "full"
|
||||
Requires-Dist: pymongo; extra == "full"
|
||||
Requires-Dist: pymysql; extra == "full"
|
||||
Requires-Dist: bs4; extra == "full"
|
||||
Requires-Dist: beautifulsoup4; extra == "full"
|
||||
Requires-Dist: feedparser; extra == "full"
|
||||
Requires-Dist: sphinx; extra == "full"
|
||||
Requires-Dist: sphinx-autobuild; extra == "full"
|
||||
Requires-Dist: sphinx_rtd_theme; extra == "full"
|
||||
Requires-Dist: myst-parser; extra == "full"
|
||||
Requires-Dist: sphinxcontrib-mermaid; extra == "full"
|
||||
Requires-Dist: pytest; extra == "full"
|
||||
Requires-Dist: pytest-cov; extra == "full"
|
||||
Requires-Dist: pre-commit; extra == "full"
|
||||
Requires-Dist: gradio==4.19.1; extra == "full"
|
||||
Requires-Dist: modelscope_studio==0.0.5; extra == "full"
|
||||
Requires-Dist: llama-index==0.10.30; extra == "full"
|
||||
|
||||
English | [**中文**](https://github.com/modelscope/agentscope/blob/main/README_ZH.md)
|
||||
|
||||
# AgentScope
|
||||
|
||||
<h1 align="left">
|
||||
<img src="https://img.alicdn.com/imgextra/i2/O1CN01cdjhVE1wwt5Auv7bY_!!6000000006373-0-tps-1792-1024.jpg" width="600" alt="agentscope-logo">
|
||||
</h1>
|
||||
|
||||
Start building LLM-empowered multi-agent applications in an easier way.
|
||||
|
||||
[](https://arxiv.org/abs/2402.14034)
|
||||
[](https://pypi.org/project/agentscope/)
|
||||
[](https://pypi.org/project/agentscope/)
|
||||
[](https://modelscope.github.io/agentscope/#welcome-to-agentscope-tutorial-hub)
|
||||
[](https://modelscope.github.io/agentscope/)
|
||||
[](https://modelscope.cn/studios?name=agentscope&page=1&sort=latest)
|
||||
|
||||
[](./LICENSE)
|
||||
[](https://modelscope.github.io/agentscope/tutorial/contribute.html)
|
||||
|
||||
If you find our work helpful, please kindly
|
||||
cite [our paper](https://arxiv.org/abs/2402.14034).
|
||||
|
||||
Welcome to join our community on
|
||||
|
||||
| [Discord](https://discord.gg/eYMpfnkG8h) | DingTalk |
|
||||
|----------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
|
||||
| <img src="https://gw.alicdn.com/imgextra/i1/O1CN01hhD1mu1Dd3BWVUvxN_!!6000000000238-2-tps-400-400.png" width="100" height="100"> | <img src="https://img.alicdn.com/imgextra/i2/O1CN01tuJ5971OmAqNg9cOw_!!6000000001747-0-tps-444-460.jpg" width="100" height="100"> |
|
||||
|
||||
----
|
||||
|
||||
## News
|
||||
|
||||
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2024-06-14]** A new prompt tuning module is available in AgentScope to help developers generate and optimize the agents' system prompts! Refer to our [tutorial](https://modelscope.github.io/agentscope/en/tutorial/209-prompt_opt.html) for more details!
|
||||
|
||||
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2024-06-11]** The RAG functionality is available for agents in **AgentScope** now! [**A quick introduction to RAG in AgentScope**](https://modelscope.github.io/agentscope/en/tutorial/210-rag.html) can help you equip your agent with external knowledge!
|
||||
|
||||
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2024-06-09]** We release **AgentScope** v0.0.5 now! In this new version, [**AgentScope Workstation**](https://modelscope.github.io/agentscope/en/tutorial/209-gui.html) (the online version is running on [agentscope.io](https://agentscope.io)) is open-sourced with the refactored [**AgentScope Studio**](https://modelscope.github.io/agentscope/en/tutorial/209-gui.html)!
|
||||
|
||||
<h5 align="center">
|
||||
<img src="https://img.alicdn.com/imgextra/i1/O1CN01RXAVVn1zUtjXVvuqS_!!6000000006718-1-tps-3116-1852.gif" width="600" alt="agentscope-logo">
|
||||
</h5>
|
||||
|
||||
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2024-05-24]** We are pleased to announce that features related to the **AgentScope Workstation** will soon be open-sourced! The online website services are temporarily offline. The online website service will be upgraded and back online shortly. Stay tuned...
|
||||
|
||||
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2024-05-15]** A new **Parser Module** for **formatted response** is added in AgentScope! Refer to our [tutorial](https://modelscope.github.io/agentscope/en/tutorial/203-parser.html) for more details. The [`DictDialogAgent`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/agents/dict_dialog_agent.py) and [werewolf game](https://github.com/modelscope/agentscope/tree/main/examples/game_werewolf) example are updated simultaneously.
|
||||
|
||||
<https://github.com/qbc2016/AgentScope/assets/22984042/22d45aee-3470-4923-850f-348a5b0faaa7>
|
||||
|
||||
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2024-05-14]** Dear AgentScope users, we are conducting a survey on **AgentScope Workstation & Copilot** user experience. We currently need your valuable feedback to help us improve the experience of AgentScope's Drag & Drop multi-agent application development and Copilot. Your feedback is valuable and the survey will take about 3~5 minutes. Please click [URL](https://survey.aliyun.com/apps/zhiliao/vgpTppn22) to participate in questionnaire surveys. Thank you very much for your support and contribution!
|
||||
|
||||
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2024-05-14]** AgentScope supports **gpt-4o** as well as other OpenAI vision models now! Try gpt-4o with its [model configuration](./examples/model_configs_template/openai_chat_template.json) and new example [Conversation with gpt-4o](./examples/conversation_with_gpt-4o)!
|
||||
|
||||
- **[2024-04-30]** We release **AgentScope** v0.0.4 now!
|
||||
|
||||
- **[2024-04-27]** [AgentScope Workstation](https://agentscope.aliyun.com/) is now online! You are welcome to try building your multi-agent application simply with our *drag-and-drop platform* and ask our *copilot* questions about AgentScope!
|
||||
|
||||
- **[2024-04-19]** AgentScope supports Llama3 now! We provide [scripts](https://github.com/modelscope/agentscope/blob/main/examples/model_llama3) and example [model configuration](https://github.com/modelscope/agentscope/blob/main/examples/model_llama3) for quick set-up. Feel free to try llama3 in our examples!
|
||||
|
||||
- **[2024-04-06]** We release **AgentScope** v0.0.3 now!
|
||||
|
||||
- **[2024-04-06]** New examples [Gomoku](https://github.com/modelscope/agentscope/blob/main/examples/game_gomoku), [Conversation with ReAct Agent](https://github.com/modelscope/agentscope/blob/main/examples/conversation_with_react_agent), [Conversation with RAG Agent](https://github.com/modelscope/agentscope/blob/main/examples/conversation_with_RAG_agents) and [Distributed Parallel Optimization](https://github.com/modelscope/agentscope/blob/main/examples/distributed_parallel_optimization) are available now!
|
||||
|
||||
- **[2024-03-19]** We release **AgentScope** v0.0.2 now! In this new version,
|
||||
AgentScope supports [ollama](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#supported-models)(A local CPU inference engine), [DashScope](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#supported-models) and Google [Gemini](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#supported-models) APIs.
|
||||
|
||||
- **[2024-03-19]** New examples ["Autonomous Conversation with Mentions"](https://github.com/modelscope/agentscope/blob/main/examples/conversation_with_mentions) and ["Basic Conversation with LangChain library"](https://github.com/modelscope/agentscope/blob/main/examples/conversation_with_langchain) are available now!
|
||||
|
||||
- **[2024-03-19]** The [Chinese tutorial](https://modelscope.github.io/agentscope/zh_CN/index.html) of AgentScope is online now!
|
||||
|
||||
- **[2024-02-27]** We release **AgentScope v0.0.1** now, which is also
|
||||
available in [PyPI](https://pypi.org/project/agentscope/)!
|
||||
- **[2024-02-14]** We release our paper "AgentScope: A Flexible yet Robust
|
||||
Multi-Agent Platform" in [arXiv](https://arxiv.org/abs/2402.14034) now!
|
||||
|
||||
---
|
||||
|
||||
## What's AgentScope?
|
||||
|
||||
AgentScope is an innovative multi-agent platform designed to empower developers
|
||||
to build multi-agent applications with large-scale models.
|
||||
It features three high-level capabilities:
|
||||
|
||||
- 🤝 **Easy-to-Use**: Designed for developers, with [fruitful components](https://modelscope.github.io/agentscope/en/tutorial/204-service.html#),
|
||||
[comprehensive documentation](https://modelscope.github.io/agentscope/en/index.html), and broad compatibility. Besides, [AgentScope Workstation](https://agentscope.aliyun.com/) provides a *drag-and-drop programming platform* and a *copilot* for beginners of AgentScope!
|
||||
|
||||
- ✅ **High Robustness**: Supporting customized fault-tolerance controls and
|
||||
retry mechanisms to enhance application stability.
|
||||
|
||||
- 🚀 **Actor-Based Distribution**: Building distributed multi-agent
|
||||
applications in a centralized programming manner for streamlined development.
|
||||
|
||||
**Supported Model Libraries**
|
||||
|
||||
AgentScope provides a list of `ModelWrapper` to support both local model
|
||||
services and third-party model APIs.
|
||||
|
||||
| API | Task | Model Wrapper | Configuration | Some Supported Models |
|
||||
|------------------------|-----------------|---------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------|
|
||||
| OpenAI API | Chat | [`OpenAIChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/openai_model.py) |[guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#openai-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/openai_chat_template.json) | gpt-4o, gpt-4, gpt-3.5-turbo, ... |
|
||||
| | Embedding | [`OpenAIEmbeddingWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/openai_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#openai-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/openai_embedding_template.json) | text-embedding-ada-002, ... |
|
||||
| | DALL·E | [`OpenAIDALLEWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/openai_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#openai-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/openai_dall_e_template.json) | dall-e-2, dall-e-3 |
|
||||
| DashScope API | Chat | [`DashScopeChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/dashscope_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#dashscope-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/dashscope_chat_template.json) | qwen-plus, qwen-max, ... |
|
||||
| | Image Synthesis | [`DashScopeImageSynthesisWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/dashscope_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#dashscope-api) <br>[template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/dashscope_image_synthesis_template.json) | wanx-v1 |
|
||||
| | Text Embedding | [`DashScopeTextEmbeddingWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/dashscope_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#dashscope-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/dashscope_text_embedding_template.json) | text-embedding-v1, text-embedding-v2, ... |
|
||||
| | Multimodal | [`DashScopeMultiModalWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/dashscope_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#dashscope-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/dashscope_multimodal_template.json) | qwen-vl-max, qwen-vl-chat-v1, qwen-audio-chat |
|
||||
| Gemini API | Chat | [`GeminiChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/gemini_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#gemini-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/gemini_chat_template.json) | gemini-pro, ... |
|
||||
| | Embedding | [`GeminiEmbeddingWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/gemini_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#gemini-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/gemini_embedding_template.json) | models/embedding-001, ... |
|
||||
| ZhipuAI API | Chat | [`ZhipuAIChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/zhipu_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#zhipu-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/zhipu_chat_template.json) | glm-4, ... |
|
||||
| | Embedding | [`ZhipuAIEmbeddingWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/zhipu_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#zhipu-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/zhipu_embedding_template.json) | embedding-2, ... |
|
||||
| ollama | Chat | [`OllamaChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/ollama_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#ollama-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/ollama_chat_template.json) | llama3, llama2, Mistral, ... |
|
||||
| | Embedding | [`OllamaEmbeddingWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/ollama_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#ollama-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/ollama_embedding_template.json) | llama2, Mistral, ... |
|
||||
| | Generation | [`OllamaGenerationWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/ollama_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#ollama-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/ollama_generate_template.json) | llama2, Mistral, ... |
|
||||
| LiteLLM API | Chat | [`LiteLLMChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/litellm_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#litellm-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/litellm_chat_template.json) | [models supported by litellm](https://docs.litellm.ai/docs/)... |
|
||||
| Post Request based API | - | [`PostAPIModelWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/post_model.py) | [guidance](https://modelscope.github.io/agentscope/en/tutorial/203-model.html#post-request-api) <br> [template](https://github.com/modelscope/agentscope/blob/main/examples/model_configs_template/postapi_model_config_template.json) | - |
|
||||
|
||||
**Supported Local Model Deployment**
|
||||
|
||||
AgentScope enables developers to rapidly deploy local model services using
|
||||
the following libraries.
|
||||
|
||||
- [ollama (CPU inference)](https://github.com/modelscope/agentscope/blob/main/scripts/README.md#ollama)
|
||||
- [Flask + Transformers](https://github.com/modelscope/agentscope/blob/main/scripts/README.md#with-transformers-library)
|
||||
- [Flask + ModelScope](https://github.com/modelscope/agentscope/blob/main/scripts/README.md#with-modelscope-library)
|
||||
- [FastChat](https://github.com/modelscope/agentscope/blob/main/scripts/README.md#fastchat)
|
||||
- [vllm](https://github.com/modelscope/agentscope/blob/main/scripts/README.md#vllm)
|
||||
|
||||
**Supported Services**
|
||||
|
||||
- Web Search
|
||||
- Data Query
|
||||
- Retrieval
|
||||
- Code Execution
|
||||
- File Operation
|
||||
- Text Processing
|
||||
- Multi Modality
|
||||
|
||||
**Example Applications**
|
||||
|
||||
- Model
|
||||
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>[Using Llama3 in AgentScope](https://github.com/modelscope/agentscope/blob/main/examples/model_llama3)
|
||||
|
||||
- Conversation
|
||||
- [Basic Conversation](https://github.com/modelscope/agentscope/blob/main/examples/conversation_basic)
|
||||
- [Autonomous Conversation with Mentions](https://github.com/modelscope/agentscope/blob/main/examples/conversation_with_mentions)
|
||||
- [Self-Organizing Conversation](https://github.com/modelscope/agentscope/blob/main/examples/conversation_self_organizing)
|
||||
- [Basic Conversation with LangChain library](https://github.com/modelscope/agentscope/blob/main/examples/conversation_with_langchain)
|
||||
- [Conversation with ReAct Agent](https://github.com/modelscope/agentscope/blob/main/examples/conversation_with_react_agent)
|
||||
- [Conversation in Natural Language to Query SQL](https://github.com/modelscope/agentscope/blob/main/examples/conversation_nl2sql/)
|
||||
- [Conversation with RAG Agent](https://github.com/modelscope/agentscope/blob/main/examples/conversation_with_RAG_agents)
|
||||
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>[Conversation with gpt-4o](https://github.com/modelscope/agentscope/blob/main/examples/conversation_with_gpt-4o)
|
||||
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>[Conversation with Software Engineering Agent](https://github.com/modelscope/agentscope/blob/main/examples/conversation_with_swe-agent/)
|
||||
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>[Conversation with Customized Services](https://github.com/modelscope/agentscope/blob/main/examples/conversation_with_customized_services/)
|
||||
|
||||
- Game
|
||||
- [Gomoku](https://github.com/modelscope/agentscope/blob/main/examples/game_gomoku)
|
||||
- [Werewolf](https://github.com/modelscope/agentscope/blob/main/examples/game_werewolf)
|
||||
|
||||
- Distribution
|
||||
- [Distributed Conversation](https://github.com/modelscope/agentscope/blob/main/examples/distributed_conversation)
|
||||
- [Distributed Debate](https://github.com/modelscope/agentscope/blob/main/examples/distributed_debate)
|
||||
- [Distributed Parallel Optimization](https://github.com/modelscope/agentscope/blob/main/examples/distributed_parallel_optimization)
|
||||
- [Distributed Large Scale Simulation](https://github.com/modelscope/agentscope/blob/main/examples/distributed_simulation)
|
||||
|
||||
More models, services and examples are coming soon!
|
||||
|
||||
## Installation
|
||||
|
||||
AgentScope requires **Python 3.9** or higher.
|
||||
|
||||
***Note: This project is currently in active development, it's recommended to
|
||||
install AgentScope from source.***
|
||||
|
||||
### From source
|
||||
|
||||
- Install AgentScope in editable mode:
|
||||
|
||||
```bash
|
||||
# Pull the source code from GitHub
|
||||
git clone https://github.com/modelscope/agentscope.git
|
||||
|
||||
# Install the package in editable mode
|
||||
cd agentscope
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
- To build distributed multi-agent applications:
|
||||
|
||||
```bash
|
||||
# On windows
|
||||
pip install -e .[distribute]
|
||||
# On mac
|
||||
pip install -e .\[distribute\]
|
||||
```
|
||||
|
||||
### Using pip
|
||||
|
||||
- Install AgentScope from pip:
|
||||
|
||||
```bash
|
||||
pip install agentscope --pre
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Configuration
|
||||
|
||||
In AgentScope, the model deployment and invocation are decoupled by
|
||||
`ModelWrapper`.
|
||||
|
||||
To use these model wrappers, you need to prepare a model config file as
|
||||
follows.
|
||||
|
||||
```python
|
||||
model_config = {
|
||||
# The identifies of your config and used model wrapper
|
||||
"config_name": "{your_config_name}", # The name to identify the config
|
||||
"model_type": "{model_type}", # The type to identify the model wrapper
|
||||
|
||||
# Detailed parameters into initialize the model wrapper
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
Taking OpenAI Chat API as an example, the model configuration is as follows:
|
||||
|
||||
```python
|
||||
openai_model_config = {
|
||||
"config_name": "my_openai_config", # The name to identify the config
|
||||
"model_type": "openai_chat", # The type to identify the model wrapper
|
||||
|
||||
# Detailed parameters into initialize the model wrapper
|
||||
"model_name": "gpt-4", # The used model in openai API, e.g. gpt-4, gpt-3.5-turbo, etc.
|
||||
"api_key": "xxx", # The API key for OpenAI API. If not set, env
|
||||
# variable OPENAI_API_KEY will be used.
|
||||
"organization": "xxx", # The organization for OpenAI API. If not set, env
|
||||
# variable OPENAI_ORGANIZATION will be used.
|
||||
}
|
||||
```
|
||||
|
||||
More details about how to set up local model services and prepare model
|
||||
configurations is in our
|
||||
[tutorial](https://modelscope.github.io/agentscope/index.html#welcome-to-agentscope-tutorial-hub).
|
||||
|
||||
### Create Agents
|
||||
|
||||
Create built-in user and assistant agents as follows.
|
||||
|
||||
```python
|
||||
from agentscope.agents import DialogAgent, UserAgent
|
||||
import agentscope
|
||||
|
||||
# Load model configs
|
||||
agentscope.init(model_configs="./model_configs.json")
|
||||
|
||||
# Create a dialog agent and a user agent
|
||||
dialog_agent = DialogAgent(name="assistant",
|
||||
model_config_name="my_openai_config")
|
||||
user_agent = UserAgent()
|
||||
```
|
||||
|
||||
### Construct Conversation
|
||||
|
||||
In AgentScope, **message** is the bridge among agents, which is a
|
||||
**dict** that contains two necessary fields `name` and `content` and an
|
||||
optional field `url` to local files (image, video or audio) or website.
|
||||
|
||||
```python
|
||||
from agentscope.message import Msg
|
||||
|
||||
x = Msg(name="Alice", content="Hi!")
|
||||
x = Msg("Bob", "What about this picture I took?", url="/path/to/picture.jpg")
|
||||
```
|
||||
|
||||
Start a conversation between two agents (e.g. dialog_agent and user_agent)
|
||||
with the following code:
|
||||
|
||||
```python
|
||||
x = None
|
||||
while True:
|
||||
x = dialog_agent(x)
|
||||
x = user_agent(x)
|
||||
if x.content == "exit": # user input "exit" to exit the conversation_basic
|
||||
break
|
||||
```
|
||||
|
||||
### AgentScope Studio
|
||||
|
||||
AgentScope provides an easy-to-use runtime user interface capable of
|
||||
displaying multimodal output on the front end, including text, images,
|
||||
audio and video.
|
||||
|
||||
Refer to our [tutorial](https://modelscope.github.io/agentscope/en/tutorial/209-gui.html) for more details.
|
||||
|
||||
<h5 align="center">
|
||||
<img src="https://img.alicdn.com/imgextra/i4/O1CN015kjnkd1xdwJoNxqLZ_!!6000000006467-0-tps-3452-1984.jpg" width="600" alt="agentscope-logo">
|
||||
</h5>
|
||||
|
||||
## Tutorial
|
||||
|
||||
- [About AgentScope](https://modelscope.github.io/agentscope/zh_CN/tutorial/101-agentscope.html)
|
||||
- [Installation](https://modelscope.github.io/agentscope/zh_CN/tutorial/102-installation.html)
|
||||
- [Quick Start](https://modelscope.github.io/agentscope/zh_CN/tutorial/103-example.html)
|
||||
- [Model](https://modelscope.github.io/agentscope/zh_CN/tutorial/203-model.html)
|
||||
- [Prompt Engineering](https://modelscope.github.io/agentscope/zh_CN/tutorial/206-prompt.html)
|
||||
- [Agent](https://modelscope.github.io/agentscope/zh_CN/tutorial/201-agent.html)
|
||||
- [Memory](https://modelscope.github.io/agentscope/zh_CN/tutorial/205-memory.html)
|
||||
- [Response Parser](https://modelscope.github.io/agentscope/zh_CN/tutorial/203-parser.html)
|
||||
- [Tool](https://modelscope.github.io/agentscope/zh_CN/tutorial/204-service.html)
|
||||
- [Pipeline and MsgHub](https://modelscope.github.io/agentscope/zh_CN/tutorial/202-pipeline.html)
|
||||
- [Distribution](https://modelscope.github.io/agentscope/zh_CN/tutorial/208-distribute.html)
|
||||
- [AgentScope Studio](https://modelscope.github.io/agentscope/zh_CN/tutorial/209-gui.html)
|
||||
- [Logging](https://modelscope.github.io/agentscope/zh_CN/tutorial/105-logging.html)
|
||||
- [Monitor](https://modelscope.github.io/agentscope/zh_CN/tutorial/207-monitor.html)
|
||||
- [Example: Werewolf Game](https://modelscope.github.io/agentscope/zh_CN/tutorial/104-usecase.html)
|
||||
|
||||
## License
|
||||
|
||||
AgentScope is released under Apache License 2.0.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are always welcomed!
|
||||
|
||||
We provide a developer version with additional pre-commit hooks to perform
|
||||
checks compared to the official version:
|
||||
|
||||
```bash
|
||||
# For windows
|
||||
pip install -e .[dev]
|
||||
# For mac
|
||||
pip install -e .\[dev\]
|
||||
|
||||
# Install pre-commit hooks
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
Please refer to our [Contribution Guide](https://modelscope.github.io/agentscope/en/tutorial/302-contribute.html) for more details.
|
||||
|
||||
## References
|
||||
|
||||
If you find our work helpful for your research or application, please
|
||||
cite [our paper](https://arxiv.org/abs/2402.14034):
|
||||
|
||||
```
|
||||
@article{agentscope,
|
||||
author = {Dawei Gao and
|
||||
Zitao Li and
|
||||
Xuchen Pan and
|
||||
Weirui Kuang and
|
||||
Zhijian Ma and
|
||||
Bingchen Qian and
|
||||
Fei Wei and
|
||||
Wenhao Zhang and
|
||||
Yuexiang Xie and
|
||||
Daoyuan Chen and
|
||||
Liuyi Yao and
|
||||
Hongyi Peng and
|
||||
Zeyu Zhang and
|
||||
Lin Zhu and
|
||||
Chen Cheng and
|
||||
Hongzhu Shi and
|
||||
Yaliang Li and
|
||||
Bolin Ding and
|
||||
Jingren Zhou},
|
||||
title = {AgentScope: A Flexible yet Robust Multi-Agent Platform},
|
||||
journal = {CoRR},
|
||||
volume = {abs/2402.14034},
|
||||
year = {2024},
|
||||
}
|
||||
```
|
||||
204
AlgoriAgent/src/agentscope.egg-info/SOURCES.txt
Normal file
204
AlgoriAgent/src/agentscope.egg-info/SOURCES.txt
Normal file
@@ -0,0 +1,204 @@
|
||||
LICENSE
|
||||
README.md
|
||||
pyproject.toml
|
||||
setup.py
|
||||
src/agentscope/__init__.py
|
||||
src/agentscope/_init.py
|
||||
src/agentscope/_runtime.py
|
||||
src/agentscope/_version.py
|
||||
src/agentscope/constants.py
|
||||
src/agentscope/exception.py
|
||||
src/agentscope/file_manager.py
|
||||
src/agentscope/logging.py
|
||||
src/agentscope/message.py
|
||||
src/agentscope/msghub.py
|
||||
src/agentscope.egg-info/PKG-INFO
|
||||
src/agentscope.egg-info/SOURCES.txt
|
||||
src/agentscope.egg-info/dependency_links.txt
|
||||
src/agentscope.egg-info/entry_points.txt
|
||||
src/agentscope.egg-info/requires.txt
|
||||
src/agentscope.egg-info/top_level.txt
|
||||
src/agentscope/agents/__init__.py
|
||||
src/agentscope/agents/agent.py
|
||||
src/agentscope/agents/dialog_agent.py
|
||||
src/agentscope/agents/dict_dialog_agent.py
|
||||
src/agentscope/agents/operator.py
|
||||
src/agentscope/agents/rag_agent.py
|
||||
src/agentscope/agents/react_agent.py
|
||||
src/agentscope/agents/rpc_agent.py
|
||||
src/agentscope/agents/text_to_image_agent.py
|
||||
src/agentscope/agents/user_agent.py
|
||||
src/agentscope/memory/__init__.py
|
||||
src/agentscope/memory/memory.py
|
||||
src/agentscope/memory/temporary_memory.py
|
||||
src/agentscope/models/__init__.py
|
||||
src/agentscope/models/config.py
|
||||
src/agentscope/models/dashscope_model.py
|
||||
src/agentscope/models/gemini_model.py
|
||||
src/agentscope/models/litellm_model.py
|
||||
src/agentscope/models/model.py
|
||||
src/agentscope/models/ollama_model.py
|
||||
src/agentscope/models/openai_model.py
|
||||
src/agentscope/models/post_model.py
|
||||
src/agentscope/models/response.py
|
||||
src/agentscope/models/zhipu_model.py
|
||||
src/agentscope/parsers/__init__.py
|
||||
src/agentscope/parsers/code_block_parser.py
|
||||
src/agentscope/parsers/json_object_parser.py
|
||||
src/agentscope/parsers/parser_base.py
|
||||
src/agentscope/parsers/tagged_content_parser.py
|
||||
src/agentscope/pipelines/__init__.py
|
||||
src/agentscope/pipelines/functional.py
|
||||
src/agentscope/pipelines/pipeline.py
|
||||
src/agentscope/prompt/__init__.py
|
||||
src/agentscope/prompt/_prompt_comparer.py
|
||||
src/agentscope/prompt/_prompt_engine.py
|
||||
src/agentscope/prompt/_prompt_examples.json
|
||||
src/agentscope/prompt/_prompt_generator_base.py
|
||||
src/agentscope/prompt/_prompt_generator_en.py
|
||||
src/agentscope/prompt/_prompt_generator_zh.py
|
||||
src/agentscope/prompt/_prompt_optimizer.py
|
||||
src/agentscope/prompt/_prompt_utils.py
|
||||
src/agentscope/rag/__init__.py
|
||||
src/agentscope/rag/knowledge.py
|
||||
src/agentscope/rag/knowledge_bank.py
|
||||
src/agentscope/rag/llama_index_knowledge.py
|
||||
src/agentscope/rpc/__init__.py
|
||||
src/agentscope/rpc/rpc_agent_client.py
|
||||
src/agentscope/rpc/rpc_agent_pb2.py
|
||||
src/agentscope/rpc/rpc_agent_pb2_grpc.py
|
||||
src/agentscope/server/__init__.py
|
||||
src/agentscope/server/launcher.py
|
||||
src/agentscope/server/servicer.py
|
||||
src/agentscope/service/__init__.py
|
||||
src/agentscope/service/service_response.py
|
||||
src/agentscope/service/service_status.py
|
||||
src/agentscope/service/service_toolkit.py
|
||||
src/agentscope/service/execute_code/__init__.py
|
||||
src/agentscope/service/execute_code/exec_python.py
|
||||
src/agentscope/service/execute_code/exec_shell.py
|
||||
src/agentscope/service/file/__init__.py
|
||||
src/agentscope/service/file/common.py
|
||||
src/agentscope/service/file/json.py
|
||||
src/agentscope/service/file/text.py
|
||||
src/agentscope/service/multi_modality/__init__.py
|
||||
src/agentscope/service/multi_modality/dashscope_services.py
|
||||
src/agentscope/service/multi_modality/openai_services.py
|
||||
src/agentscope/service/retrieval/__init__.py
|
||||
src/agentscope/service/retrieval/retrieval_from_list.py
|
||||
src/agentscope/service/retrieval/similarity.py
|
||||
src/agentscope/service/sql_query/__init__.py
|
||||
src/agentscope/service/sql_query/mongodb.py
|
||||
src/agentscope/service/sql_query/mysql.py
|
||||
src/agentscope/service/sql_query/sqlite.py
|
||||
src/agentscope/service/text_processing/__init__.py
|
||||
src/agentscope/service/text_processing/summarization.py
|
||||
src/agentscope/service/web/__init__.py
|
||||
src/agentscope/service/web/arxiv.py
|
||||
src/agentscope/service/web/dblp.py
|
||||
src/agentscope/service/web/download.py
|
||||
src/agentscope/service/web/search.py
|
||||
src/agentscope/service/web/web_digest.py
|
||||
src/agentscope/studio/__init__.py
|
||||
src/agentscope/studio/_app.py
|
||||
src/agentscope/studio/_client.py
|
||||
src/agentscope/studio/static/css/base.css
|
||||
src/agentscope/studio/static/css/dashboard-detail-code.css
|
||||
src/agentscope/studio/static/css/dashboard-detail-dialogue.css
|
||||
src/agentscope/studio/static/css/dashboard-detail-invocation.css
|
||||
src/agentscope/studio/static/css/dashboard-detail.css
|
||||
src/agentscope/studio/static/css/dashboard-runs.css
|
||||
src/agentscope/studio/static/css/dashboard.css
|
||||
src/agentscope/studio/static/css/font.css
|
||||
src/agentscope/studio/static/css/index.css
|
||||
src/agentscope/studio/static/css/workstation-drag-components.css
|
||||
src/agentscope/studio/static/css/workstation.css
|
||||
src/agentscope/studio/static/css_third_party/clusterize.css
|
||||
src/agentscope/studio/static/css_third_party/drawflow.min.css
|
||||
src/agentscope/studio/static/css_third_party/highlight.min.css
|
||||
src/agentscope/studio/static/css_third_party/katex.min.css
|
||||
src/agentscope/studio/static/css_third_party/prism-line-numbers.min.css
|
||||
src/agentscope/studio/static/css_third_party/prism.min.css
|
||||
src/agentscope/studio/static/css_third_party/tabulator.min.css
|
||||
src/agentscope/studio/static/fonts/KRYPTON.ttf
|
||||
src/agentscope/studio/static/fonts/SourceSans3-Regular.ttf
|
||||
src/agentscope/studio/static/html/dashboard-detail-code.html
|
||||
src/agentscope/studio/static/html/dashboard-detail-dialogue.html
|
||||
src/agentscope/studio/static/html/dashboard-detail-invocation.html
|
||||
src/agentscope/studio/static/html/dashboard-detail.html
|
||||
src/agentscope/studio/static/html/dashboard-runs.html
|
||||
src/agentscope/studio/static/html/dashboard.html
|
||||
src/agentscope/studio/static/html/index-guide.html
|
||||
src/agentscope/studio/static/html/market.html
|
||||
src/agentscope/studio/static/html/server.html
|
||||
src/agentscope/studio/static/html/template.html
|
||||
src/agentscope/studio/static/html/workstation_iframe.html
|
||||
src/agentscope/studio/static/html-drag-components/agent-dialogagent.html
|
||||
src/agentscope/studio/static/html-drag-components/agent-dictdialogagent.html
|
||||
src/agentscope/studio/static/html-drag-components/agent-reactagent.html
|
||||
src/agentscope/studio/static/html-drag-components/agent-texttoimageagent.html
|
||||
src/agentscope/studio/static/html-drag-components/agent-useragent.html
|
||||
src/agentscope/studio/static/html-drag-components/message-msg.html
|
||||
src/agentscope/studio/static/html-drag-components/model-dashscope-chat.html
|
||||
src/agentscope/studio/static/html-drag-components/model-openai-chat.html
|
||||
src/agentscope/studio/static/html-drag-components/model-post-api-chat.html
|
||||
src/agentscope/studio/static/html-drag-components/model-post-api-dall-e.html
|
||||
src/agentscope/studio/static/html-drag-components/pipeline-forlooppipeline.html
|
||||
src/agentscope/studio/static/html-drag-components/pipeline-ifelsepipeline.html
|
||||
src/agentscope/studio/static/html-drag-components/pipeline-msghub.html
|
||||
src/agentscope/studio/static/html-drag-components/pipeline-placeholder.html
|
||||
src/agentscope/studio/static/html-drag-components/pipeline-sequentialpipeline.html
|
||||
src/agentscope/studio/static/html-drag-components/pipeline-switchpipeline.html
|
||||
src/agentscope/studio/static/html-drag-components/pipeline-whilelooppipeline.html
|
||||
src/agentscope/studio/static/html-drag-components/service-bing-search.html
|
||||
src/agentscope/studio/static/html-drag-components/service-execute-python.html
|
||||
src/agentscope/studio/static/html-drag-components/service-google-search.html
|
||||
src/agentscope/studio/static/html-drag-components/service-read-text.html
|
||||
src/agentscope/studio/static/html-drag-components/service-write-text.html
|
||||
src/agentscope/studio/static/html-drag-components/welcome.html
|
||||
src/agentscope/studio/static/js/dashboard-detail-code.js
|
||||
src/agentscope/studio/static/js/dashboard-detail-dialogue.js
|
||||
src/agentscope/studio/static/js/dashboard-detail-invocation.js
|
||||
src/agentscope/studio/static/js/dashboard-detail.js
|
||||
src/agentscope/studio/static/js/dashboard-runs.js
|
||||
src/agentscope/studio/static/js/dashboard.js
|
||||
src/agentscope/studio/static/js/index.js
|
||||
src/agentscope/studio/static/js/workstation.js
|
||||
src/agentscope/studio/static/js/workstation_iframe.js
|
||||
src/agentscope/studio/static/js_third_party/clusterize.min.js
|
||||
src/agentscope/studio/static/js_third_party/drawflow.min.js
|
||||
src/agentscope/studio/static/js_third_party/highlight.min.js
|
||||
src/agentscope/studio/static/js_third_party/jquery.min.js
|
||||
src/agentscope/studio/static/js_third_party/katex.min.js
|
||||
src/agentscope/studio/static/js_third_party/marked-katex-extension.umd.js
|
||||
src/agentscope/studio/static/js_third_party/marked.min.js
|
||||
src/agentscope/studio/static/js_third_party/micromodal.min.js
|
||||
src/agentscope/studio/static/js_third_party/monaco-editor.loader.js
|
||||
src/agentscope/studio/static/js_third_party/prism-line-numbers.min.js
|
||||
src/agentscope/studio/static/js_third_party/prism-python.min.js
|
||||
src/agentscope/studio/static/js_third_party/prism.min.js
|
||||
src/agentscope/studio/static/js_third_party/socket.io.js
|
||||
src/agentscope/studio/static/js_third_party/sweetalert2@9
|
||||
src/agentscope/studio/static/js_third_party/tabulator.min.js
|
||||
src/agentscope/studio/static/workstation_templates/en1.json
|
||||
src/agentscope/studio/static/workstation_templates/en2.json
|
||||
src/agentscope/studio/static/workstation_templates/en3.json
|
||||
src/agentscope/studio/static/workstation_templates/en4.json
|
||||
src/agentscope/studio/static/workstation_templates/zh1.json
|
||||
src/agentscope/studio/templates/index.html
|
||||
src/agentscope/studio/templates/workstation.html
|
||||
src/agentscope/utils/__init__.py
|
||||
src/agentscope/utils/common.py
|
||||
src/agentscope/utils/monitor.py
|
||||
src/agentscope/utils/token_utils.py
|
||||
src/agentscope/utils/tools.py
|
||||
src/agentscope/web/__init__.py
|
||||
src/agentscope/web/gradio/__init__.py
|
||||
src/agentscope/web/gradio/constants.py
|
||||
src/agentscope/web/gradio/studio.py
|
||||
src/agentscope/web/gradio/utils.py
|
||||
src/agentscope/web/workstation/__init__.py
|
||||
src/agentscope/web/workstation/workflow.py
|
||||
src/agentscope/web/workstation/workflow_dag.py
|
||||
src/agentscope/web/workstation/workflow_node.py
|
||||
src/agentscope/web/workstation/workflow_utils.py
|
||||
1
AlgoriAgent/src/agentscope.egg-info/dependency_links.txt
Normal file
1
AlgoriAgent/src/agentscope.egg-info/dependency_links.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
5
AlgoriAgent/src/agentscope.egg-info/entry_points.txt
Normal file
5
AlgoriAgent/src/agentscope.egg-info/entry_points.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
[console_scripts]
|
||||
as_gradio = agentscope.web.gradio.studio:run_app
|
||||
as_server = agentscope.server.launcher:as_server
|
||||
as_studio = agentscope.studio:init
|
||||
as_workflow = agentscope.web.workstation.workflow:main
|
||||
133
AlgoriAgent/src/agentscope.egg-info/requires.txt
Normal file
133
AlgoriAgent/src/agentscope.egg-info/requires.txt
Normal file
@@ -0,0 +1,133 @@
|
||||
networkx
|
||||
black
|
||||
docstring_parser
|
||||
pydantic
|
||||
loguru==0.6.0
|
||||
tiktoken
|
||||
Pillow
|
||||
requests
|
||||
chardet
|
||||
inputimeout
|
||||
openai>=1.3.0
|
||||
numpy
|
||||
Flask==3.0.0
|
||||
Flask-Cors==4.0.0
|
||||
Flask-SocketIO==5.3.6
|
||||
flask_sqlalchemy
|
||||
flake8
|
||||
dashscope==1.14.1
|
||||
openai>=1.3.0
|
||||
ollama>=0.1.7
|
||||
google-generativeai>=0.4.0
|
||||
zhipuai
|
||||
litellm
|
||||
psutil
|
||||
scipy
|
||||
|
||||
[dev]
|
||||
networkx
|
||||
black
|
||||
docstring_parser
|
||||
pydantic
|
||||
loguru==0.6.0
|
||||
tiktoken
|
||||
Pillow
|
||||
requests
|
||||
chardet
|
||||
inputimeout
|
||||
openai>=1.3.0
|
||||
numpy
|
||||
Flask==3.0.0
|
||||
Flask-Cors==4.0.0
|
||||
Flask-SocketIO==5.3.6
|
||||
flask_sqlalchemy
|
||||
flake8
|
||||
dashscope==1.14.1
|
||||
ollama>=0.1.7
|
||||
google-generativeai>=0.4.0
|
||||
zhipuai
|
||||
litellm
|
||||
psutil
|
||||
scipy
|
||||
pytest
|
||||
pytest-cov
|
||||
pre-commit
|
||||
|
||||
[distribute]
|
||||
networkx
|
||||
black
|
||||
docstring_parser
|
||||
pydantic
|
||||
loguru==0.6.0
|
||||
tiktoken
|
||||
Pillow
|
||||
requests
|
||||
chardet
|
||||
inputimeout
|
||||
openai>=1.3.0
|
||||
numpy
|
||||
Flask==3.0.0
|
||||
Flask-Cors==4.0.0
|
||||
Flask-SocketIO==5.3.6
|
||||
flask_sqlalchemy
|
||||
flake8
|
||||
dashscope==1.14.1
|
||||
ollama>=0.1.7
|
||||
google-generativeai>=0.4.0
|
||||
zhipuai
|
||||
litellm
|
||||
psutil
|
||||
scipy
|
||||
grpcio==1.60.0
|
||||
grpcio-tools==1.60.0
|
||||
protobuf==4.25.0
|
||||
expiringdict
|
||||
dill
|
||||
|
||||
[full]
|
||||
networkx
|
||||
black
|
||||
docstring_parser
|
||||
pydantic
|
||||
loguru==0.6.0
|
||||
tiktoken
|
||||
Pillow
|
||||
requests
|
||||
chardet
|
||||
inputimeout
|
||||
openai>=1.3.0
|
||||
numpy
|
||||
Flask==3.0.0
|
||||
Flask-Cors==4.0.0
|
||||
Flask-SocketIO==5.3.6
|
||||
flask_sqlalchemy
|
||||
flake8
|
||||
dashscope==1.14.1
|
||||
ollama>=0.1.7
|
||||
google-generativeai>=0.4.0
|
||||
zhipuai
|
||||
litellm
|
||||
psutil
|
||||
scipy
|
||||
grpcio==1.60.0
|
||||
grpcio-tools==1.60.0
|
||||
protobuf==4.25.0
|
||||
expiringdict
|
||||
dill
|
||||
docker
|
||||
pymongo
|
||||
pymysql
|
||||
bs4
|
||||
beautifulsoup4
|
||||
feedparser
|
||||
sphinx
|
||||
sphinx-autobuild
|
||||
sphinx_rtd_theme
|
||||
myst-parser
|
||||
sphinxcontrib-mermaid
|
||||
pytest
|
||||
pytest-cov
|
||||
pre-commit
|
||||
gradio==4.19.1
|
||||
modelscope_studio==0.0.5
|
||||
llama-index==0.10.30
|
||||
1
AlgoriAgent/src/agentscope.egg-info/top_level.txt
Normal file
1
AlgoriAgent/src/agentscope.egg-info/top_level.txt
Normal file
@@ -0,0 +1 @@
|
||||
agentscope
|
||||
26
AlgoriAgent/src/agentscope/__init__.py
Normal file
26
AlgoriAgent/src/agentscope/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Import all modules in the package. """
|
||||
|
||||
# modules
|
||||
from . import agents
|
||||
from . import memory
|
||||
from . import models
|
||||
from . import pipelines
|
||||
from . import service
|
||||
from . import message
|
||||
from . import prompt
|
||||
from . import web
|
||||
from . import exception
|
||||
from . import parsers
|
||||
from . import rag
|
||||
|
||||
# objects or function
|
||||
from .msghub import msghub
|
||||
from ._version import __version__
|
||||
from ._init import init
|
||||
|
||||
|
||||
__all__ = [
|
||||
"init",
|
||||
"msghub",
|
||||
]
|
||||
BIN
AlgoriAgent/src/agentscope/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/_init.cpython-310.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/_init.cpython-310.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/_init.cpython-311.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/_init.cpython-311.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/_runtime.cpython-310.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/_runtime.cpython-310.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/_runtime.cpython-311.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/_runtime.cpython-311.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/_version.cpython-310.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/_version.cpython-310.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/_version.cpython-311.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/_version.cpython-311.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/constants.cpython-310.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/constants.cpython-310.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/constants.cpython-311.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/constants.cpython-311.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/exception.cpython-310.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/exception.cpython-310.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/exception.cpython-311.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/exception.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/logging.cpython-310.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/logging.cpython-310.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/logging.cpython-311.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/logging.cpython-311.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/message.cpython-310.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/message.cpython-310.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/message.cpython-311.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/message.cpython-311.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/msghub.cpython-310.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/msghub.cpython-310.pyc
Normal file
Binary file not shown.
BIN
AlgoriAgent/src/agentscope/__pycache__/msghub.cpython-311.pyc
Normal file
BIN
AlgoriAgent/src/agentscope/__pycache__/msghub.cpython-311.pyc
Normal file
Binary file not shown.
204
AlgoriAgent/src/agentscope/_init.py
Normal file
204
AlgoriAgent/src/agentscope/_init.py
Normal file
@@ -0,0 +1,204 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""The init function for the package."""
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from typing import Optional, Union, Sequence
|
||||
from agentscope import agents
|
||||
from .agents import AgentBase
|
||||
from ._runtime import _runtime
|
||||
from .file_manager import file_manager
|
||||
from .logging import LOG_LEVEL, setup_logger
|
||||
from .utils.monitor import MonitorFactory
|
||||
from .models import read_model_configs
|
||||
from .constants import _DEFAULT_DIR
|
||||
from .constants import _DEFAULT_LOG_LEVEL
|
||||
from .studio._client import _studio_client
|
||||
|
||||
# init setting
|
||||
_INIT_SETTINGS = {}
|
||||
|
||||
|
||||
def init(
|
||||
model_configs: Optional[Union[dict, str, list]] = None,
|
||||
project: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
save_dir: str = _DEFAULT_DIR,
|
||||
save_log: bool = True,
|
||||
save_code: bool = True,
|
||||
save_api_invoke: bool = False,
|
||||
use_monitor: bool = True,
|
||||
logger_level: LOG_LEVEL = _DEFAULT_LOG_LEVEL,
|
||||
runtime_id: Optional[str] = None,
|
||||
agent_configs: Optional[Union[str, list, dict]] = None,
|
||||
studio_url: Optional[str] = None,
|
||||
) -> Sequence[AgentBase]:
|
||||
"""A unified entry to initialize the package, including model configs,
|
||||
runtime names, saving directories and logging settings.
|
||||
|
||||
Args:
|
||||
model_configs (`Optional[Union[dict, str, list]]`, defaults to `None`):
|
||||
A dict, a list of dicts, or a path to a json file containing
|
||||
model configs.
|
||||
project (`Optional[str]`, defaults to `None`):
|
||||
The project name, which is used to identify the project.
|
||||
name (`Optional[str]`, defaults to `None`):
|
||||
The name for runtime, which is used to identify this runtime.
|
||||
runtime_id (`Optional[str]`, defaults to `None`):
|
||||
The id for runtime, which is used to identify this runtime. Use
|
||||
`None` will generate a random id.
|
||||
save_dir (`str`, defaults to `./runs`):
|
||||
The directory to save logs, files, codes, and api invocations.
|
||||
If `dir` is `None`, when saving logs, files, codes, and api
|
||||
invocations, the default directory `./runs` will be created.
|
||||
save_log (`bool`, defaults to `False`):
|
||||
Whether to save logs locally.
|
||||
save_code (`bool`, defaults to `False`):
|
||||
Whether to save codes locally.
|
||||
save_api_invoke (`bool`, defaults to `False`):
|
||||
Whether to save api invocations locally, including model and web
|
||||
search invocation.
|
||||
use_monitor (`bool`, defaults to `True`):
|
||||
Whether to activate the monitor.
|
||||
logger_level (`LOG_LEVEL`, defaults to `"INFO"`):
|
||||
The logging level of logger.
|
||||
agent_configs (`Optional[Union[str, list, dict]]`, defaults to `None`):
|
||||
The config dict(s) of agents or the path to the config file,
|
||||
which can be loaded by json.loads(). One agent config should
|
||||
cover the required arguments to initialize a specific agent
|
||||
object, otherwise the default values will be used.
|
||||
studio_url (`Optional[str]`, defaults to `None`):
|
||||
The url of the agentscope studio.
|
||||
"""
|
||||
init_process(
|
||||
model_configs=model_configs,
|
||||
project=project,
|
||||
name=name,
|
||||
runtime_id=runtime_id,
|
||||
save_dir=save_dir,
|
||||
save_api_invoke=save_api_invoke,
|
||||
save_log=save_log,
|
||||
use_monitor=use_monitor,
|
||||
logger_level=logger_level,
|
||||
studio_url=studio_url,
|
||||
)
|
||||
|
||||
# save init settings for subprocess
|
||||
_INIT_SETTINGS["model_configs"] = model_configs
|
||||
_INIT_SETTINGS["project"] = _runtime.project
|
||||
_INIT_SETTINGS["name"] = _runtime.name
|
||||
_INIT_SETTINGS["runtime_id"] = _runtime.runtime_id
|
||||
_INIT_SETTINGS["save_dir"] = save_dir
|
||||
_INIT_SETTINGS["save_api_invoke"] = save_api_invoke
|
||||
_INIT_SETTINGS["save_log"] = save_log
|
||||
_INIT_SETTINGS["logger_level"] = logger_level
|
||||
_INIT_SETTINGS["use_monitor"] = use_monitor
|
||||
|
||||
# Save code if needed
|
||||
if save_code:
|
||||
# Copy python file in os.path.curdir into runtime directory
|
||||
cur_dir = os.path.abspath(os.path.curdir)
|
||||
for filename in os.listdir(cur_dir):
|
||||
if filename.endswith(".py"):
|
||||
file_abs = os.path.join(cur_dir, filename)
|
||||
shutil.copy(file_abs, str(file_manager.dir_code))
|
||||
|
||||
# Load config and init agent by configs
|
||||
if agent_configs is not None:
|
||||
if isinstance(agent_configs, str):
|
||||
with open(agent_configs, "r", encoding="utf-8") as file:
|
||||
configs = json.load(file)
|
||||
elif isinstance(agent_configs, dict):
|
||||
configs = [agent_configs]
|
||||
else:
|
||||
configs = agent_configs
|
||||
|
||||
# setup agents
|
||||
agent_objs = []
|
||||
for config in configs:
|
||||
agent_cls = getattr(agents, config["class"])
|
||||
agent_args = config["args"]
|
||||
agent = agent_cls(**agent_args)
|
||||
agent_objs.append(agent)
|
||||
return agent_objs
|
||||
return []
|
||||
|
||||
|
||||
def init_process(
|
||||
model_configs: Optional[Union[dict, str, list]] = None,
|
||||
project: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
runtime_id: Optional[str] = None,
|
||||
save_dir: str = _DEFAULT_DIR,
|
||||
save_api_invoke: bool = False,
|
||||
save_log: bool = False,
|
||||
use_monitor: bool = True,
|
||||
logger_level: LOG_LEVEL = _DEFAULT_LOG_LEVEL,
|
||||
studio_url: Optional[str] = None,
|
||||
) -> None:
|
||||
"""An entry to initialize the package in a process.
|
||||
|
||||
Args:
|
||||
project (`Optional[str]`, defaults to `None`):
|
||||
The project name, which is used to identify the project.
|
||||
name (`Optional[str]`, defaults to `None`):
|
||||
The name for runtime, which is used to identify this runtime.
|
||||
runtime_id (`Optional[str]`, defaults to `None`):
|
||||
The id for runtime, which is used to identify this runtime.
|
||||
save_dir (`str`, defaults to `./runs`):
|
||||
The directory to save logs, files, codes, and api invocations.
|
||||
If `dir` is `None`, when saving logs, files, codes, and api
|
||||
invocations, the default directory `./runs` will be created.
|
||||
save_api_invoke (`bool`, defaults to `False`):
|
||||
Whether to save api invocations locally, including model and web
|
||||
search invocation.
|
||||
model_configs (`Optional[Sequence]`, defaults to `None`):
|
||||
A sequence of pre-init model configs.
|
||||
save_log (`bool`, defaults to `False`):
|
||||
Whether to save logs locally.
|
||||
use_monitor (`bool`, defaults to `True`):
|
||||
Whether to activate the monitor.
|
||||
logger_level (`LOG_LEVEL`, defaults to `"INFO"`):
|
||||
The logging level of logger.
|
||||
studio_url (`Optional[str]`, defaults to `None`):
|
||||
The url of the agentscope studio.
|
||||
"""
|
||||
# Init the runtime
|
||||
if project is not None:
|
||||
_runtime.project = project
|
||||
|
||||
if name is not None:
|
||||
_runtime.name = name
|
||||
|
||||
if runtime_id is not None:
|
||||
_runtime.runtime_id = runtime_id
|
||||
|
||||
# Init file manager and save configs by default
|
||||
file_manager.init(save_dir, save_api_invoke)
|
||||
|
||||
# Init logger
|
||||
dir_log = str(file_manager.dir_log) if save_log else None
|
||||
setup_logger(dir_log, logger_level)
|
||||
|
||||
# Load model configs if needed
|
||||
if model_configs is not None:
|
||||
read_model_configs(model_configs)
|
||||
|
||||
# Init monitor
|
||||
_ = MonitorFactory.get_monitor(
|
||||
db_path=file_manager.path_db,
|
||||
impl_type="sqlite" if use_monitor else "dummy",
|
||||
)
|
||||
|
||||
# Init studio client, which will push messages to web ui and fetch user
|
||||
# inputs from web ui
|
||||
if studio_url is not None:
|
||||
_studio_client.initialize(_runtime.runtime_id, studio_url)
|
||||
# Register in AgentScope Studio
|
||||
_studio_client.register_running_instance(
|
||||
project=_runtime.project,
|
||||
name=_runtime.name,
|
||||
timestamp=_runtime.timestamp,
|
||||
run_dir=file_manager.dir_root,
|
||||
pid=os.getpid(),
|
||||
)
|
||||
87
AlgoriAgent/src/agentscope/_runtime.py
Normal file
87
AlgoriAgent/src/agentscope/_runtime.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Manage the id for each runtime"""
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from agentscope.utils.tools import _get_timestamp
|
||||
from agentscope.utils.tools import _get_process_creation_time
|
||||
from agentscope.utils.tools import _generate_random_code
|
||||
|
||||
_RUNTIME_ID_FORMAT = "run_%Y%m%d-%H%M%S_{}"
|
||||
_RUNTIME_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
|
||||
class _Runtime:
|
||||
"""A singleton class used to record the runtime information, which will
|
||||
be initialized when the package is imported."""
|
||||
|
||||
project: str
|
||||
"""The project name, which is used to identify the project."""
|
||||
|
||||
name: str
|
||||
"""The name for runtime, which is used to identify this runtime."""
|
||||
|
||||
runtime_id: str
|
||||
"""The id for runtime, which is used to identify the this runtime and
|
||||
name the saving directory."""
|
||||
|
||||
pid: int
|
||||
"""The process id of the runtime."""
|
||||
|
||||
_timestamp: datetime
|
||||
"""The timestamp of when the runtime is initialized."""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Any:
|
||||
"""Create a singleton instance."""
|
||||
if not cls._instance:
|
||||
cls._instance = super(_Runtime, cls).__new__(
|
||||
cls,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Generate random project name, runtime name and default
|
||||
runtime_id when the package is initialized. After that, user can set
|
||||
them by calling `agentscope.init(project="xxx", name="xxx",
|
||||
runtime_id="xxx")`."""
|
||||
|
||||
self.project = _generate_random_code()
|
||||
self.name = _generate_random_code(uppercase=False)
|
||||
|
||||
self.pid = os.getpid()
|
||||
self._timestamp = _get_process_creation_time()
|
||||
|
||||
# Obtain time from timestamp in string format, and then turn it into
|
||||
# runtime ID format
|
||||
self.runtime_id = _get_timestamp(
|
||||
_RUNTIME_ID_FORMAT,
|
||||
self._timestamp,
|
||||
).format(self.name)
|
||||
|
||||
def generate_new_runtime_id(self) -> str:
|
||||
"""Generate a new random runtime id."""
|
||||
return _get_timestamp(_RUNTIME_ID_FORMAT).format(
|
||||
_generate_random_code(uppercase=False),
|
||||
)
|
||||
|
||||
@property
|
||||
def timestamp(self) -> str:
|
||||
"""Get the current timestamp in specific format."""
|
||||
return self._timestamp.strftime(_RUNTIME_TIMESTAMP_FORMAT)
|
||||
|
||||
@staticmethod
|
||||
def _flush() -> None:
|
||||
"""
|
||||
Only for unittest usage. Don't use this function in your code.
|
||||
Flush the runtime singleton.
|
||||
"""
|
||||
global _runtime
|
||||
_runtime = _Runtime()
|
||||
|
||||
|
||||
_runtime = _Runtime()
|
||||
4
AlgoriAgent/src/agentscope/_version.py
Normal file
4
AlgoriAgent/src/agentscope/_version.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Version of AgentScope."""
|
||||
|
||||
__version__ = "0.0.6alpha3"
|
||||
25
AlgoriAgent/src/agentscope/agents/__init__.py
Normal file
25
AlgoriAgent/src/agentscope/agents/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Import all agent related modules in the package. """
|
||||
from .agent import AgentBase, DistConf
|
||||
from .operator import Operator
|
||||
from .dialog_agent import DialogAgent
|
||||
from .dict_dialog_agent import DictDialogAgent
|
||||
from .user_agent import UserAgent
|
||||
from .text_to_image_agent import TextToImageAgent
|
||||
from .rpc_agent import RpcAgent
|
||||
from .react_agent import ReActAgent
|
||||
from .rag_agent import LlamaIndexAgent
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AgentBase",
|
||||
"Operator",
|
||||
"DialogAgent",
|
||||
"DictDialogAgent",
|
||||
"TextToImageAgent",
|
||||
"UserAgent",
|
||||
"ReActAgent",
|
||||
"DistConf",
|
||||
"RpcAgent",
|
||||
"LlamaIndexAgent",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
458
AlgoriAgent/src/agentscope/agents/agent.py
Normal file
458
AlgoriAgent/src/agentscope/agents/agent.py
Normal file
@@ -0,0 +1,458 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Base class for Agent """
|
||||
|
||||
from __future__ import annotations
|
||||
from abc import ABCMeta
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Union
|
||||
from typing import Any
|
||||
from typing import Type
|
||||
import uuid
|
||||
from loguru import logger
|
||||
|
||||
from agentscope.agents.operator import Operator
|
||||
from agentscope.message import Msg
|
||||
from agentscope.models import load_model_by_config_name
|
||||
from agentscope.memory import TemporaryMemory
|
||||
|
||||
|
||||
class _AgentMeta(ABCMeta):
|
||||
"""The meta-class for agent.
|
||||
|
||||
1. record the init args into `_init_settings` field.
|
||||
2. register class name into `registry` field.
|
||||
"""
|
||||
|
||||
def __init__(cls, name: Any, bases: Any, attrs: Any) -> None:
|
||||
if not hasattr(cls, "_registry"):
|
||||
cls._registry = {}
|
||||
else:
|
||||
if name in cls._registry:
|
||||
logger.warning(
|
||||
f"Agent class with name [{name}] already exists.",
|
||||
)
|
||||
else:
|
||||
cls._registry[name] = cls
|
||||
super().__init__(name, bases, attrs)
|
||||
|
||||
def __call__(cls, *args: tuple, **kwargs: dict) -> Any:
|
||||
to_dist = kwargs.pop("to_dist", False)
|
||||
if to_dist is True:
|
||||
to_dist = DistConf()
|
||||
if to_dist is not False and to_dist is not None:
|
||||
from .rpc_agent import RpcAgent
|
||||
|
||||
if cls is not RpcAgent and not issubclass(cls, RpcAgent):
|
||||
return RpcAgent(
|
||||
name=(
|
||||
args[0]
|
||||
if len(args) > 0
|
||||
else kwargs["name"] # type: ignore[arg-type]
|
||||
),
|
||||
host=to_dist.pop( # type: ignore[arg-type]
|
||||
"host",
|
||||
"localhost",
|
||||
),
|
||||
port=to_dist.pop("port", None), # type: ignore[arg-type]
|
||||
max_pool_size=kwargs.pop( # type: ignore[arg-type]
|
||||
"max_pool_size",
|
||||
8192,
|
||||
),
|
||||
max_timeout_seconds=to_dist.pop( # type: ignore[arg-type]
|
||||
"max_timeout_seconds",
|
||||
1800,
|
||||
),
|
||||
local_mode=to_dist.pop( # type: ignore[arg-type]
|
||||
"local_mode",
|
||||
True,
|
||||
),
|
||||
lazy_launch=to_dist.pop( # type: ignore[arg-type]
|
||||
"lazy_launch",
|
||||
True,
|
||||
),
|
||||
agent_id=cls.generate_agent_id(),
|
||||
connect_existing=False,
|
||||
agent_class=cls,
|
||||
agent_configs={
|
||||
"args": args,
|
||||
"kwargs": kwargs,
|
||||
"class_name": cls.__name__,
|
||||
},
|
||||
)
|
||||
instance = super().__call__(*args, **kwargs)
|
||||
instance._init_settings = {
|
||||
"args": args,
|
||||
"kwargs": kwargs,
|
||||
"class_name": cls.__name__,
|
||||
}
|
||||
return instance
|
||||
|
||||
|
||||
class DistConf(dict):
|
||||
"""Distribution configuration for agents."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str = "localhost",
|
||||
port: int = None,
|
||||
max_pool_size: int = 8192,
|
||||
max_timeout_seconds: int = 1800,
|
||||
local_mode: bool = True,
|
||||
lazy_launch: bool = True,
|
||||
):
|
||||
"""Init the distributed configuration.
|
||||
|
||||
Args:
|
||||
host (`str`, defaults to `"localhost"`):
|
||||
Hostname of the rpc agent server.
|
||||
port (`int`, defaults to `None`):
|
||||
Port of the rpc agent server.
|
||||
max_pool_size (`int`, defaults to `8192`):
|
||||
Max number of task results that the server can accommodate.
|
||||
max_timeout_seconds (`int`, defaults to `1800`):
|
||||
Timeout for task results.
|
||||
local_mode (`bool`, defaults to `True`):
|
||||
Whether the started rpc server only listens to local
|
||||
requests.
|
||||
lazy_launch (`bool`, defaults to `True`):
|
||||
Only launch the server when the agent is called.
|
||||
"""
|
||||
self["host"] = host
|
||||
self["port"] = port
|
||||
self["max_pool_size"] = max_pool_size
|
||||
self["max_timeout_seconds"] = max_timeout_seconds
|
||||
self["local_mode"] = local_mode
|
||||
self["lazy_launch"] = lazy_launch
|
||||
|
||||
|
||||
class AgentBase(Operator, metaclass=_AgentMeta):
|
||||
"""Base class for all agents.
|
||||
|
||||
All agents should inherit from this class and implement the `reply`
|
||||
function.
|
||||
"""
|
||||
|
||||
_version: int = 1
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
sys_prompt: Optional[str] = None,
|
||||
model_config_name: str = None,
|
||||
use_memory: bool = True,
|
||||
memory_config: Optional[dict] = None,
|
||||
to_dist: Optional[Union[DistConf, bool]] = False,
|
||||
) -> None:
|
||||
r"""Initialize an agent from the given arguments.
|
||||
|
||||
Args:
|
||||
name (`str`):
|
||||
The name of the agent.
|
||||
sys_prompt (`Optional[str]`):
|
||||
The system prompt of the agent, which can be passed by args
|
||||
or hard-coded in the agent.
|
||||
model_config_name (`str`, defaults to None):
|
||||
The name of the model config, which is used to load model from
|
||||
configuration.
|
||||
use_memory (`bool`, defaults to `True`):
|
||||
Whether the agent has memory.
|
||||
memory_config (`Optional[dict]`):
|
||||
The config of memory.
|
||||
to_dist (`Optional[Union[DistConf, bool]]`, default to `False`):
|
||||
The configurations passed to :py:meth:`to_dist` method. Used in
|
||||
:py:class:`_AgentMeta`, when this parameter is provided,
|
||||
the agent will automatically be converted into its distributed
|
||||
version. Below are some examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# run as a sub process
|
||||
agent = XXXAgent(
|
||||
# ... other parameters
|
||||
to_dist=True,
|
||||
)
|
||||
|
||||
# connect to an existing agent server
|
||||
agent = XXXAgent(
|
||||
# ... other parameters
|
||||
to_dist=DistConf(
|
||||
host="<ip of your server>",
|
||||
port=<port of your server>,
|
||||
# other parameters
|
||||
),
|
||||
)
|
||||
|
||||
See :doc:`Tutorial<tutorial/208-distribute>` for detail.
|
||||
"""
|
||||
self.name = name
|
||||
self.memory_config = memory_config
|
||||
|
||||
if sys_prompt is not None:
|
||||
self.sys_prompt = sys_prompt
|
||||
|
||||
# TODO: support to receive a ModelWrapper instance
|
||||
if model_config_name is not None:
|
||||
self.model = load_model_by_config_name(model_config_name)
|
||||
|
||||
if use_memory:
|
||||
self.memory = TemporaryMemory(memory_config)
|
||||
else:
|
||||
self.memory = None
|
||||
|
||||
# The global unique id of this agent
|
||||
self._agent_id = self.__class__.generate_agent_id()
|
||||
|
||||
# The audience of this agent, which means if this agent generates a
|
||||
# response, it will be passed to all agents in the audience.
|
||||
self._audience = None
|
||||
# convert to distributed agent, conversion is in `_AgentMeta`
|
||||
if to_dist is not False and to_dist is not None:
|
||||
logger.info(
|
||||
f"Convert {self.__class__.__name__}[{self.name}] into"
|
||||
" a distributed agent.",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def generate_agent_id(cls) -> str:
|
||||
"""Generate the agent_id of this agent instance"""
|
||||
# TODO: change cls.__name__ into a global unique agent_type
|
||||
return f"{cls.__name__}_{uuid.uuid4().hex}"
|
||||
|
||||
# todo: add a unique agent_type field to distinguish different agent class
|
||||
@classmethod
|
||||
def get_agent_class(cls, agent_class_name: str) -> Type[AgentBase]:
|
||||
"""Get the agent class based on the specific agent class name.
|
||||
|
||||
Args:
|
||||
agent_class_name (`str`): the name of the agent class.
|
||||
|
||||
Raises:
|
||||
ValueError: Agent class name not exits.
|
||||
|
||||
Returns:
|
||||
Type[AgentBase]: the AgentBase sub-class.
|
||||
"""
|
||||
if agent_class_name not in cls._registry:
|
||||
raise ValueError(f"Agent [{agent_class_name}] not found.")
|
||||
return cls._registry[agent_class_name] # type: ignore[return-value]
|
||||
|
||||
@classmethod
|
||||
def register_agent_class(cls, agent_class: Type[AgentBase]) -> None:
|
||||
"""Register the agent class into the registry.
|
||||
|
||||
Args:
|
||||
agent_class (Type[AgentBase]): the agent class to be registered.
|
||||
"""
|
||||
agent_class_name = agent_class.__name__
|
||||
if agent_class_name in cls._registry:
|
||||
logger.info(
|
||||
f"Agent class with name [{agent_class_name}] already exists.",
|
||||
)
|
||||
else:
|
||||
cls._registry[agent_class_name] = agent_class
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None, user_backboard:str = "") -> Msg:
|
||||
"""Define the actions taken by this agent.
|
||||
|
||||
Args:
|
||||
x (`Optional[Union[Msg, Sequence[Msg]]]`, defaults to `None`):
|
||||
The input message(s) to the agent, which also can be omitted if
|
||||
the agent doesn't need any input.
|
||||
user_backboard (`str`): The user backboard, including user's IDE files in brief.
|
||||
|
||||
Returns:
|
||||
`Msg`: The output message generated by the agent.
|
||||
|
||||
Note:
|
||||
Given that some agents are in an adversarial environment,
|
||||
their input doesn't include the thoughts of other agents.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
f"Agent [{type(self).__name__}] is missing the required "
|
||||
f'"reply" function.',
|
||||
)
|
||||
|
||||
def load_from_config(self, config: dict) -> None:
|
||||
"""Load configuration for this agent.
|
||||
|
||||
Args:
|
||||
config (`dict`): model configuration
|
||||
"""
|
||||
|
||||
def export_config(self) -> dict:
|
||||
"""Return configuration of this agent.
|
||||
|
||||
Returns:
|
||||
The configuration of current agent.
|
||||
"""
|
||||
return {}
|
||||
|
||||
def load_memory(self, memory: Sequence[dict]) -> None:
|
||||
r"""Load input memory."""
|
||||
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> dict:
|
||||
"""Calling the reply function, and broadcast the generated
|
||||
response to all audiences if needed."""
|
||||
res = self.reply(*args, **kwargs)
|
||||
|
||||
# broadcast to audiences if needed
|
||||
if self._audience is not None:
|
||||
self._broadcast_to_audience(res)
|
||||
|
||||
return res
|
||||
|
||||
def speak(
|
||||
self,
|
||||
content: Union[str, Msg],
|
||||
) -> None:
|
||||
"""
|
||||
Speak out the message generated by the agent. If a string is given,
|
||||
a Msg object will be created with the string as the content.
|
||||
|
||||
Args:
|
||||
content (`Union[str, Msg]`):
|
||||
The content of the message to be spoken out. If a string is
|
||||
given, a Msg object will be created with the agent's name, role
|
||||
as "assistant", and the given string as the content.
|
||||
"""
|
||||
if isinstance(content, str):
|
||||
msg = Msg(
|
||||
name=self.name,
|
||||
content=content,
|
||||
role="assistant",
|
||||
)
|
||||
elif isinstance(content, Msg):
|
||||
msg = content
|
||||
else:
|
||||
raise TypeError(
|
||||
"From version 0.0.5, the speak method only accepts str or Msg "
|
||||
f"object, got {type(content)} instead.",
|
||||
)
|
||||
|
||||
logger.chat(msg)
|
||||
|
||||
def observe(self, x: Union[dict, Sequence[dict]]) -> None:
|
||||
"""Observe the input, store it in memory without response to it.
|
||||
|
||||
Args:
|
||||
x (`Union[dict, Sequence[dict]]`):
|
||||
The input message to be recorded in memory.
|
||||
"""
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
|
||||
def reset_audience(self, audience: Sequence[AgentBase]) -> None:
|
||||
"""Set the audience of this agent, which means if this agent
|
||||
generates a response, it will be passed to all audiences.
|
||||
|
||||
Args:
|
||||
audience (`Sequence[AgentBase]`):
|
||||
The audience of this agent, which will be notified when this
|
||||
agent generates a response message.
|
||||
"""
|
||||
# TODO: we leave the consideration of nested msghub for future.
|
||||
# for now we suppose one agent can only be in one msghub
|
||||
self._audience = [_ for _ in audience if _ != self]
|
||||
|
||||
def clear_audience(self) -> None:
|
||||
"""Remove the audience of this agent."""
|
||||
# TODO: we leave the consideration of nested msghub for future.
|
||||
# for now we suppose one agent can only be in one msghub
|
||||
self._audience = None
|
||||
|
||||
def rm_audience(
|
||||
self,
|
||||
audience: Union[Sequence[AgentBase], AgentBase],
|
||||
) -> None:
|
||||
"""Remove the given audience from the Sequence"""
|
||||
if not isinstance(audience, Sequence):
|
||||
audience = [audience]
|
||||
|
||||
for agent in audience:
|
||||
if self._audience is not None and agent in self._audience:
|
||||
self._audience.pop(self._audience.index(agent))
|
||||
else:
|
||||
logger.warning(
|
||||
f"Skip removing agent [{agent.name}] from the "
|
||||
f"audience for its inexistence.",
|
||||
)
|
||||
|
||||
def _broadcast_to_audience(self, x: dict) -> None:
|
||||
"""Broadcast the input to all audiences."""
|
||||
for agent in self._audience:
|
||||
agent.observe(x)
|
||||
|
||||
@property
|
||||
def agent_id(self) -> str:
|
||||
"""The unique id of this agent.
|
||||
|
||||
Returns:
|
||||
str: agent_id
|
||||
"""
|
||||
return self._agent_id
|
||||
|
||||
def to_dist(
|
||||
self,
|
||||
host: str = "localhost",
|
||||
port: int = None,
|
||||
max_pool_size: int = 8192,
|
||||
max_timeout_seconds: int = 1800,
|
||||
local_mode: bool = True,
|
||||
lazy_launch: bool = True,
|
||||
launch_server: bool = None,
|
||||
) -> AgentBase:
|
||||
"""Convert current agent instance into a distributed version.
|
||||
|
||||
Args:
|
||||
host (`str`, defaults to `"localhost"`):
|
||||
Hostname of the rpc agent server.
|
||||
port (`int`, defaults to `None`):
|
||||
Port of the rpc agent server.
|
||||
max_pool_size (`int`, defaults to `8192`):
|
||||
Only takes effect when `host` and `port` are not filled in.
|
||||
The max number of agent reply messages that the started agent
|
||||
server can accommodate. Note that the oldest message will be
|
||||
deleted after exceeding the pool size.
|
||||
max_timeout_seconds (`int`, defaults to `1800`):
|
||||
Only takes effect when `host` and `port` are not filled in.
|
||||
Maximum time for reply messages to be cached in the launched
|
||||
agent server. Note that expired messages will be deleted.
|
||||
local_mode (`bool`, defaults to `True`):
|
||||
Only takes effect when `host` and `port` are not filled in.
|
||||
Whether the started agent server only listens to local
|
||||
requests.
|
||||
lazy_launch (`bool`, defaults to `True`):
|
||||
Only takes effect when `host` and `port` are not filled in.
|
||||
If `True`, launch the agent server when the agent is called,
|
||||
otherwise, launch the agent server immediately.
|
||||
launch_server(`bool`, defaults to `None`):
|
||||
This field has been deprecated and will be removed in
|
||||
future releases.
|
||||
|
||||
Returns:
|
||||
`AgentBase`: the wrapped agent instance with distributed
|
||||
functionality
|
||||
"""
|
||||
from .rpc_agent import RpcAgent
|
||||
|
||||
if issubclass(self.__class__, RpcAgent):
|
||||
return self
|
||||
if launch_server is not None:
|
||||
logger.warning(
|
||||
"`launch_server` has been deprecated and will be removed in "
|
||||
"future releases. When `host` and `port` is not provided, the "
|
||||
"agent server will be launched automatically.",
|
||||
)
|
||||
return RpcAgent(
|
||||
name=self.name,
|
||||
agent_class=self.__class__,
|
||||
agent_configs=self._init_settings,
|
||||
host=host,
|
||||
port=port,
|
||||
max_pool_size=max_pool_size,
|
||||
max_timeout_seconds=max_timeout_seconds,
|
||||
local_mode=local_mode,
|
||||
lazy_launch=lazy_launch,
|
||||
agent_id=self.agent_id,
|
||||
)
|
||||
82
AlgoriAgent/src/agentscope/agents/dialog_agent.py
Normal file
82
AlgoriAgent/src/agentscope/agents/dialog_agent.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""A general dialog agent."""
|
||||
from typing import Optional, Union, Sequence
|
||||
|
||||
from ..message import Msg
|
||||
from .agent import AgentBase
|
||||
|
||||
|
||||
class DialogAgent(AgentBase):
|
||||
"""A simple agent used to perform a dialogue. Your can set its role by
|
||||
`sys_prompt`."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
sys_prompt: str,
|
||||
model_config_name: str,
|
||||
use_memory: bool = True,
|
||||
memory_config: Optional[dict] = None,
|
||||
) -> None:
|
||||
"""Initialize the dialog agent.
|
||||
|
||||
Arguments:
|
||||
name (`str`):
|
||||
The name of the agent.
|
||||
sys_prompt (`Optional[str]`):
|
||||
The system prompt of the agent, which can be passed by args
|
||||
or hard-coded in the agent.
|
||||
model_config_name (`str`):
|
||||
The name of the model config, which is used to load model from
|
||||
configuration.
|
||||
use_memory (`bool`, defaults to `True`):
|
||||
Whether the agent has memory.
|
||||
memory_config (`Optional[dict]`):
|
||||
The config of memory.
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
sys_prompt=sys_prompt,
|
||||
model_config_name=model_config_name,
|
||||
use_memory=use_memory,
|
||||
memory_config=memory_config,
|
||||
)
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
"""Reply function of the agent. Processes the input data,
|
||||
generates a prompt using the current dialogue memory and system
|
||||
prompt, and invokes the language model to produce a response. The
|
||||
response is then formatted and added to the dialogue memory.
|
||||
|
||||
Args:
|
||||
x (`Optional[Union[Msg, Sequence[Msg]]]`, defaults to `None`):
|
||||
The input message(s) to the agent, which also can be omitted if
|
||||
the agent doesn't need any input.
|
||||
|
||||
Returns:
|
||||
`Msg`: The output message generated by the agent.
|
||||
"""
|
||||
# record the input if needed
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
|
||||
# prepare prompt
|
||||
prompt = self.model.format(
|
||||
Msg("system", self.sys_prompt, role="system"),
|
||||
self.memory
|
||||
and self.memory.get_memory()
|
||||
or x, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
# call llm and generate response
|
||||
response = self.model(prompt).text
|
||||
msg = Msg(self.name, response, role="assistant")
|
||||
|
||||
# Print/speak the message in this agent's voice
|
||||
self.speak(msg)
|
||||
|
||||
# Record the message in memory
|
||||
if self.memory:
|
||||
self.memory.add(msg)
|
||||
|
||||
return msg
|
||||
123
AlgoriAgent/src/agentscope/agents/dict_dialog_agent.py
Normal file
123
AlgoriAgent/src/agentscope/agents/dict_dialog_agent.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""An agent that replies in a dictionary format."""
|
||||
from typing import Optional, Union, Sequence
|
||||
|
||||
from ..message import Msg
|
||||
from .agent import AgentBase
|
||||
from ..parsers import ParserBase
|
||||
|
||||
|
||||
class DictDialogAgent(AgentBase):
|
||||
"""An agent that generates response in a dict format, where user can
|
||||
specify the required fields in the response via specifying the parser
|
||||
|
||||
About parser, please refer to our
|
||||
[tutorial](https://modelscope.github.io/agentscope/en/tutorial/203-parser.html)
|
||||
|
||||
For usage example, please refer to the example of werewolf in
|
||||
`examples/game_werewolf`"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
sys_prompt: str,
|
||||
model_config_name: str,
|
||||
use_memory: bool = True,
|
||||
memory_config: Optional[dict] = None,
|
||||
max_retries: Optional[int] = 3,
|
||||
) -> None:
|
||||
"""Initialize the dict dialog agent.
|
||||
|
||||
Arguments:
|
||||
name (`str`):
|
||||
The name of the agent.
|
||||
sys_prompt (`Optional[str]`, defaults to `None`):
|
||||
The system prompt of the agent, which can be passed by args
|
||||
or hard-coded in the agent.
|
||||
model_config_name (`str`, defaults to None):
|
||||
The name of the model config, which is used to load model from
|
||||
configuration.
|
||||
use_memory (`bool`, defaults to `True`):
|
||||
Whether the agent has memory.
|
||||
memory_config (`Optional[dict]`, defaults to `None`):
|
||||
The config of memory.
|
||||
max_retries (`Optional[int]`, defaults to `None`):
|
||||
The maximum number of retries when failed to parse the model
|
||||
output.
|
||||
""" # noqa
|
||||
super().__init__(
|
||||
name=name,
|
||||
sys_prompt=sys_prompt,
|
||||
model_config_name=model_config_name,
|
||||
use_memory=use_memory,
|
||||
memory_config=memory_config,
|
||||
)
|
||||
|
||||
self.parser = None
|
||||
self.max_retries = max_retries
|
||||
|
||||
def set_parser(self, parser: ParserBase) -> None:
|
||||
"""Set response parser, which will provide 1) format instruction; 2)
|
||||
response parsing; 3) filtering fields when returning message, storing
|
||||
message in memory. So developers only need to change the
|
||||
parser, and the agent will work as expected.
|
||||
"""
|
||||
self.parser = parser
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
"""Reply function of the agent.
|
||||
Processes the input data, generates a prompt using the current
|
||||
dialogue memory and system prompt, and invokes the language
|
||||
model to produce a response. The response is then formatted
|
||||
and added to the dialogue memory.
|
||||
|
||||
Args:
|
||||
x (`Optional[Union[Msg, Sequence[Msg]]]`, defaults to `None`):
|
||||
The input message(s) to the agent, which also can be omitted if
|
||||
the agent doesn't need any input.
|
||||
|
||||
|
||||
Returns:
|
||||
`Msg`: The output message generated by the agent.
|
||||
|
||||
Raises:
|
||||
`json.decoder.JSONDecodeError`:
|
||||
If the response from the language model is not valid JSON,
|
||||
it defaults to treating the response as plain text.
|
||||
"""
|
||||
# record the input if needed
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
|
||||
# prepare prompt
|
||||
prompt = self.model.format(
|
||||
Msg("system", self.sys_prompt, role="system"),
|
||||
self.memory
|
||||
and self.memory.get_memory()
|
||||
or x, # type: ignore[arg-type]
|
||||
Msg("system", self.parser.format_instruction, "system"),
|
||||
)
|
||||
|
||||
# call llm
|
||||
res = self.model(
|
||||
prompt,
|
||||
parse_func=self.parser.parse,
|
||||
max_retries=self.max_retries,
|
||||
)
|
||||
|
||||
# Filter the parsed response by keys for storing in memory, returning
|
||||
# in the reply function, and feeding into the metadata field in the
|
||||
# returned message object.
|
||||
self.memory.add(
|
||||
Msg(self.name, self.parser.to_memory(res.parsed), "assistant"),
|
||||
)
|
||||
|
||||
msg = Msg(
|
||||
self.name,
|
||||
content=self.parser.to_content(res.parsed),
|
||||
role="assistant",
|
||||
metadata=self.parser.to_metadata(res.parsed),
|
||||
)
|
||||
self.speak(msg)
|
||||
|
||||
return msg
|
||||
18
AlgoriAgent/src/agentscope/agents/operator.py
Normal file
18
AlgoriAgent/src/agentscope/agents/operator.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""A common base class for AgentBase and PipelineBase"""
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Operator(ABC):
|
||||
"""
|
||||
Abstract base class `Operator` defines a protocol for classes that
|
||||
implement callable behavior.
|
||||
The class is designed to be subclassed with an overridden `__call__`
|
||||
method that specifies the execution logic for the operator.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> dict:
|
||||
"""Calling function"""
|
||||
194
AlgoriAgent/src/agentscope/agents/rag_agent.py
Normal file
194
AlgoriAgent/src/agentscope/agents/rag_agent.py
Normal file
@@ -0,0 +1,194 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This example shows how to build an agent with RAG
|
||||
with LlamaIndex.
|
||||
|
||||
Notice, this is a Beta version of RAG agent.
|
||||
"""
|
||||
|
||||
from typing import Any, Optional, Union, Sequence
|
||||
from loguru import logger
|
||||
|
||||
from agentscope.agents.agent import AgentBase
|
||||
from agentscope.message import Msg
|
||||
from agentscope.rag import Knowledge
|
||||
|
||||
CHECKING_PROMPT = """
|
||||
Is the retrieved content relevant to the query?
|
||||
Retrieved content: {}
|
||||
Query: {}
|
||||
Only answer YES or NO.
|
||||
"""
|
||||
|
||||
|
||||
class LlamaIndexAgent(AgentBase):
|
||||
"""
|
||||
A LlamaIndex agent build on LlamaIndex.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
sys_prompt: str,
|
||||
model_config_name: str,
|
||||
knowledge_list: list[Knowledge] = None,
|
||||
knowledge_id_list: list[str] = None,
|
||||
similarity_top_k: int = None,
|
||||
log_retrieval: bool = True,
|
||||
recent_n_mem_for_retrieve: int = 1,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize the RAG LlamaIndexAgent
|
||||
Args:
|
||||
name (str):
|
||||
the name for the agent
|
||||
sys_prompt (str):
|
||||
system prompt for the RAG agent
|
||||
model_config_name (str):
|
||||
language model for the agent
|
||||
knowledge_list (list[Knowledge]):
|
||||
a list of knowledge.
|
||||
User can choose to pass a list knowledge object
|
||||
directly when initializing the RAG agent. Another
|
||||
choice can be passing a list of knowledge ids and
|
||||
obtain the knowledge with the `equip` function of a
|
||||
knowledge bank.
|
||||
knowledge_id_list (list[Knowledge]):
|
||||
a list of id of the knowledge.
|
||||
This is designed for easy setting up multiple RAG
|
||||
agents with a config file. To obtain the knowledge
|
||||
objects, users can pass this agent to the `equip`
|
||||
function in a knowledge bank to add corresponding
|
||||
knowledge to agent's self.knowledge_list.
|
||||
similarity_top_k (int):
|
||||
the number of most similar data blocks retrieved
|
||||
from each of the knowledge
|
||||
log_retrieval (bool):
|
||||
whether to print the retrieved content
|
||||
recent_n_mem_for_retrieve (int):
|
||||
the number of pieces of memory used as part of
|
||||
retrival query
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
sys_prompt=sys_prompt,
|
||||
model_config_name=model_config_name,
|
||||
)
|
||||
self.knowledge_list = knowledge_list or []
|
||||
self.knowledge_id_list = knowledge_id_list or []
|
||||
self.similarity_top_k = similarity_top_k
|
||||
self.log_retrieval = log_retrieval
|
||||
self.recent_n_mem_for_retrieve = recent_n_mem_for_retrieve
|
||||
self.description = kwargs.get("description", "")
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
"""
|
||||
Reply function of the RAG agent.
|
||||
Processes the input data,
|
||||
1) use the input data to retrieve with RAG function;
|
||||
2) generates a prompt using the current memory and system
|
||||
prompt;
|
||||
3) invokes the language model to produce a response. The
|
||||
response is then formatted and added to the dialogue memory.
|
||||
|
||||
Args:
|
||||
x (`Optional[Union[Msg, Sequence[Msg]]]`, defaults to `None`):
|
||||
The input message(s) to the agent, which also can be omitted if
|
||||
the agent doesn't need any input.
|
||||
|
||||
Returns:
|
||||
`Msg`: The output message generated by the agent.
|
||||
"""
|
||||
retrieved_docs_to_string = ""
|
||||
# record the input if needed
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
# in case no input is provided (e.g., in msghub),
|
||||
# use the memory as query
|
||||
history = self.memory.get_memory(
|
||||
recent_n=self.recent_n_mem_for_retrieve,
|
||||
)
|
||||
query = (
|
||||
"/n".join(
|
||||
[msg["content"] for msg in history],
|
||||
)
|
||||
if isinstance(history, list)
|
||||
else str(history)
|
||||
)
|
||||
elif x is not None:
|
||||
query = x.content
|
||||
else:
|
||||
query = ""
|
||||
|
||||
if len(query) > 0:
|
||||
# when content has information, do retrieval
|
||||
scores = []
|
||||
for knowledge in self.knowledge_list:
|
||||
retrieved_nodes = knowledge.retrieve(
|
||||
str(query),
|
||||
self.similarity_top_k,
|
||||
)
|
||||
for node in retrieved_nodes:
|
||||
scores.append(node.score)
|
||||
retrieved_docs_to_string += (
|
||||
"\n>>>> score:"
|
||||
+ str(node.score)
|
||||
+ "\n>>>> source:"
|
||||
+ str(node.node.get_metadata_str())
|
||||
+ "\n>>>> content:"
|
||||
+ node.get_content()
|
||||
)
|
||||
|
||||
if self.log_retrieval:
|
||||
self.speak("[retrieved]:" + retrieved_docs_to_string)
|
||||
|
||||
if max(scores) < 0.4:
|
||||
# if the max score is lower than 0.4, then we let LLM
|
||||
# decide whether the retrieved content is relevant
|
||||
# to the user input.
|
||||
msg = Msg(
|
||||
name="user",
|
||||
role="user",
|
||||
content=CHECKING_PROMPT.format(
|
||||
retrieved_docs_to_string,
|
||||
query,
|
||||
),
|
||||
)
|
||||
msg = self.model.format(msg)
|
||||
checking = self.model(msg)
|
||||
logger.info(checking)
|
||||
checking = checking.text.lower()
|
||||
if "no" in checking:
|
||||
retrieved_docs_to_string = "EMPTY"
|
||||
|
||||
# prepare prompt
|
||||
prompt = self.model.format(
|
||||
Msg(
|
||||
name="system",
|
||||
role="system",
|
||||
content=self.sys_prompt,
|
||||
),
|
||||
# {"role": "system", "content": retrieved_docs_to_string},
|
||||
self.memory.get_memory(
|
||||
recent_n=self.recent_n_mem_for_retrieve,
|
||||
),
|
||||
Msg(
|
||||
name="user",
|
||||
role="user",
|
||||
content="Context: " + retrieved_docs_to_string,
|
||||
),
|
||||
)
|
||||
|
||||
# call llm and generate response
|
||||
response = self.model(prompt).text
|
||||
msg = Msg(self.name, response)
|
||||
|
||||
# Print/speak the message in this agent's voice
|
||||
self.speak(msg)
|
||||
|
||||
if self.memory:
|
||||
# Record the message in memory
|
||||
self.memory.add(msg)
|
||||
|
||||
return msg
|
||||
316
AlgoriAgent/src/agentscope/agents/react_agent.py
Normal file
316
AlgoriAgent/src/agentscope/agents/react_agent.py
Normal file
@@ -0,0 +1,316 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""An agent class that implements the ReAct algorithm. The agent will reason
|
||||
and act iteratively to solve problems. More details can be found in the paper
|
||||
https://arxiv.org/abs/2210.03629.
|
||||
"""
|
||||
from typing import Any, Optional, Union, Sequence
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from agentscope.exception import ResponseParsingError, FunctionCallError
|
||||
from agentscope.agents import AgentBase
|
||||
from agentscope.message import Msg
|
||||
from agentscope.parsers import MarkdownJsonDictParser
|
||||
from agentscope.service import ServiceToolkit
|
||||
from agentscope.service.service_toolkit import ServiceFunction
|
||||
|
||||
INSTRUCTION_PROMPT = """## Your Role:
|
||||
You are a powerful AI agent and specifically aimed to use C# and Unity to create new Game Contents, which in the game project are named as 'Gurouce'.
|
||||
Gurouce is refer to anything that can be USED to make some influence in game, and your main job is follow user's instruction to create new game contents,
|
||||
This game is a 2D game in Unity with C# scripts.
|
||||
|
||||
## What You Should Do:
|
||||
0. Check user's instruction, make sure you know where the game project, and user's instruction is rational and can be realize. Otherwize, just finish.
|
||||
1. To realize a new Gurouce, you should first analyze the user's task, to answer three main questions:
|
||||
A. How the Gurouce find the target to influence?
|
||||
B. At what situation the Gurouce will start to influence?
|
||||
C. How the Gurouce influence the target?
|
||||
2. Then, open and read the source file, then inherit from them and override their functions to realize the three main questions:
|
||||
A. BaseFindTarget.cs
|
||||
B. BaseStartCondition.cs
|
||||
C. BaseInfluence.cs
|
||||
You should open and read the example Gurouce to learn how to use. The example Gurouce is RockGurouce, when using it, it will create a "Rock" Gurouce and fly from the game character towards where the mouse pointer is, and cause damage to the target it hits, then destory itself.
|
||||
3. Since you have to write some source files, make sure each file implement their job functionality, and all three questions above are anwsered well.
|
||||
3. Next, Call "compile" function, it will compile the source files and update game contents.
|
||||
4. If failed to compile, follow the error message and edit again, otherwise finish.
|
||||
|
||||
## Note:
|
||||
1. Fully understand the tool functions and their arguments before using them.
|
||||
2. Check the folder structure and relevant scripts before write any code.
|
||||
3. Your EVERY response MUST have at least one function call, and the function call must be in the "function" field.
|
||||
4. Make sure the types and values of the arguments you provided to the tool functions are correct.
|
||||
5. Don't take things for granted. For example, where you are, what's the time now, etc. You can try to use the tool functions to get information.
|
||||
6. If the function execution fails, you should analyze the error and try to solve it.
|
||||
7. You don't have to finish the whole task quickly, we will ask your several times, so follow the "What You Should Do" and where we have done, to respond once a step.
|
||||
|
||||
|
||||
## Resources:
|
||||
1. The tool functions you can use.
|
||||
2. A Specific Unity game project.
|
||||
|
||||
## Constraint:
|
||||
1. You should not use any other tool functions.
|
||||
2. There is a folder path 'Assets/HotUpdate/Gurouce/', you must create a subfolder in it, and you can only edit within the subfolder. But you can read any other file if need. BTW, the base scripts are in 'BaseScripts' subfolder and RockGurouce is in 'RockGurouce' subfolder.
|
||||
3. You have to decide a Gurouce name, and you have to change "Base" to the Gurouce name you decided when you write the source files' names.
|
||||
""" # noqa
|
||||
|
||||
|
||||
class ReActAgent(AgentBase):
|
||||
"""An agent class that implements the ReAct algorithm. More details refer
|
||||
to https://arxiv.org/abs/2210.03629.
|
||||
|
||||
Note this is an example implementation of ReAct algorithm in AgentScope.
|
||||
We follow the idea within the paper, but the detailed prompt engineering
|
||||
maybe different. Developers are encouraged to modify the prompt to fit
|
||||
their own needs.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
model_config_name: str,
|
||||
service_toolkit: ServiceToolkit = None,
|
||||
sys_prompt: str = "You're a helpful assistant. Your name is {name}.",
|
||||
max_iters: int = 10,
|
||||
verbose: bool = True,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the ReAct agent with the given name, model config name
|
||||
and tools.
|
||||
|
||||
Args:
|
||||
name (`str`):
|
||||
The name of the agent.
|
||||
sys_prompt (`str`):
|
||||
The system prompt of the agent.
|
||||
model_config_name (`str`):
|
||||
The name of the model config, which is used to load model from
|
||||
configuration.
|
||||
service_toolkit (`ServiceToolkit`):
|
||||
A `ServiceToolkit` object that contains the tool functions.
|
||||
max_iters (`int`, defaults to `10`):
|
||||
The maximum number of iterations of the reasoning-acting loops.
|
||||
verbose (`bool`, defaults to `True`):
|
||||
Whether to print the detailed information during reasoning and
|
||||
acting steps. If `False`, only the content in speak field will
|
||||
be print out.
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
sys_prompt=sys_prompt,
|
||||
model_config_name=model_config_name,
|
||||
)
|
||||
|
||||
# TODO: To compatible with the old version, which will be deprecated
|
||||
# soon
|
||||
if "tools" in kwargs:
|
||||
logger.warning(
|
||||
"The argument `tools` will be deprecated soon. "
|
||||
"Please use `service_toolkit` instead. Example refers to "
|
||||
"https://github.com/modelscope/agentscope/blob/main/"
|
||||
"examples/conversation_with_react_agent/code/"
|
||||
"conversation_with_react_agent.py",
|
||||
)
|
||||
|
||||
service_funcs = {}
|
||||
for func, json_schema in kwargs["tools"]:
|
||||
name = json_schema["function"]["name"]
|
||||
service_funcs[name] = ServiceFunction(
|
||||
name=name,
|
||||
original_func=func,
|
||||
processed_func=func,
|
||||
json_schema=json_schema,
|
||||
)
|
||||
|
||||
if service_toolkit is None:
|
||||
service_toolkit = ServiceToolkit()
|
||||
service_toolkit.service_funcs = service_funcs
|
||||
else:
|
||||
service_toolkit.service_funcs.update(service_funcs)
|
||||
|
||||
elif service_toolkit is None:
|
||||
raise ValueError(
|
||||
"The argument `service_toolkit` is required to initialize "
|
||||
"the ReActAgent.",
|
||||
)
|
||||
|
||||
self.service_toolkit = service_toolkit
|
||||
self.verbose = verbose
|
||||
self.max_iters = max_iters
|
||||
|
||||
if not sys_prompt.endswith("\n"):
|
||||
sys_prompt = sys_prompt + "\n"
|
||||
|
||||
self.sys_prompt = "\n".join(
|
||||
[
|
||||
# The brief intro of the role and target
|
||||
sys_prompt.format(name=self.name),
|
||||
# The instruction prompt for tools
|
||||
self.service_toolkit.tools_instruction,
|
||||
# The detailed instruction prompt for the agent
|
||||
INSTRUCTION_PROMPT,
|
||||
],
|
||||
)
|
||||
|
||||
# Put sys prompt into memory
|
||||
self.memory.add(Msg("system", self.sys_prompt, role="system"))
|
||||
|
||||
# Initialize a parser object to formulate the response from the model
|
||||
self.parser = MarkdownJsonDictParser(
|
||||
content_hint={
|
||||
"thought": "what you thought",
|
||||
"speak": "what you speak",
|
||||
"function": service_toolkit.tools_calling_format,
|
||||
},
|
||||
required_keys=["thought", "speak", "function"],
|
||||
# Only print the speak field when verbose is False
|
||||
keys_to_content=True if self.verbose else "speak",
|
||||
)
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
"""The reply function that achieves the ReAct algorithm.
|
||||
The more details please refer to https://arxiv.org/abs/2210.03629"""
|
||||
|
||||
self.memory.add(x)
|
||||
|
||||
for _ in range(self.max_iters):
|
||||
# Step 1: Thought
|
||||
if self.verbose:
|
||||
self.speak(f" ITER {_+1}, STEP 1: REASONING ".center(70, "#"))
|
||||
|
||||
# Prepare hint to remind model what the response format is
|
||||
# Won't be recorded in memory to save tokens
|
||||
hint_msg = Msg(
|
||||
"system",
|
||||
self.parser.format_instruction,
|
||||
role="system",
|
||||
echo=self.verbose,
|
||||
)
|
||||
|
||||
# Prepare prompt for the model
|
||||
prompt = self.model.format(self.memory.get_memory(), hint_msg)
|
||||
print('====================================Prompt==========================================')
|
||||
print(prompt)
|
||||
# Generate and parse the response
|
||||
try:
|
||||
res = self.model(
|
||||
prompt,
|
||||
parse_func=self.parser.parse,
|
||||
max_retries=1,
|
||||
)
|
||||
|
||||
print('====================================Result Return==========================================')
|
||||
print(res)
|
||||
print('====================================Result Parsed==========================================')
|
||||
print(res.parsed)
|
||||
# Record the response in memory
|
||||
self.memory.add(
|
||||
Msg(
|
||||
self.name,
|
||||
self.parser.to_memory(res.parsed),
|
||||
"assistant",
|
||||
),
|
||||
)
|
||||
|
||||
# Print out the response
|
||||
msg_returned = Msg(
|
||||
self.name,
|
||||
self.parser.to_content(res.parsed),
|
||||
"assistant",
|
||||
)
|
||||
print('====================================Speak==========================================')
|
||||
print(msg_returned)
|
||||
self.speak(msg_returned)
|
||||
|
||||
# Skip the next steps if no need to call tools
|
||||
# The parsed field is a dictionary
|
||||
print('====================================Function==========================================')
|
||||
print(res.parsed["function"])
|
||||
arg_function = res.parsed["function"]
|
||||
if (
|
||||
isinstance(arg_function, str)
|
||||
and arg_function in ["[]", ""]
|
||||
or isinstance(arg_function, list)
|
||||
and len(arg_function) == 0
|
||||
):
|
||||
# Only the speak field is exposed to users or other agents
|
||||
return msg_returned
|
||||
|
||||
# Only catch the response parsing error and expose runtime
|
||||
# errors to developers for debugging
|
||||
except ResponseParsingError as e:
|
||||
# Print out raw response from models for developers to debug
|
||||
response_msg = Msg(self.name, e.raw_response, "assistant")
|
||||
self.speak(response_msg)
|
||||
|
||||
# Re-correct by model itself
|
||||
error_msg = Msg("system", str(e), "system")
|
||||
self.speak(error_msg)
|
||||
|
||||
self.memory.add([response_msg, error_msg])
|
||||
|
||||
# Skip acting step to re-correct the response
|
||||
continue
|
||||
|
||||
# Step 2: Acting
|
||||
if self.verbose:
|
||||
self.speak(f" ITER {_+1}, STEP 2: ACTING ".center(70, "#"))
|
||||
|
||||
# Parse, check and execute the tool functions in service toolkit
|
||||
try:
|
||||
|
||||
if res.parsed["function"] == "finish":
|
||||
hint_msg = Msg(
|
||||
"system",
|
||||
"You have Finished the task."
|
||||
"Now generate a reply by summarizing the current "
|
||||
"situation.",
|
||||
role="system",
|
||||
echo=self.verbose,
|
||||
)
|
||||
|
||||
# Generate a reply by summarizing the current situation
|
||||
prompt = self.model.format(self.memory.get_memory(), hint_msg)
|
||||
res = self.model(prompt)
|
||||
res_msg = Msg(self.name, res.text, "assistant")
|
||||
self.speak(res_msg)
|
||||
return res_msg
|
||||
|
||||
execute_results = self.service_toolkit.parse_and_call_func(
|
||||
res.parsed["function"],
|
||||
)
|
||||
|
||||
# Note: Observing the execution results and generate response
|
||||
# are finished in the next reasoning step. We just put the
|
||||
# execution results into memory, and wait for the next loop
|
||||
# to generate response.
|
||||
|
||||
# Record execution results into memory as system message
|
||||
msg_res = Msg("system", execute_results, "system")
|
||||
self.speak(msg_res)
|
||||
self.memory.add(msg_res)
|
||||
|
||||
except FunctionCallError as e:
|
||||
# Catch the function calling error that can be handled by
|
||||
# the model
|
||||
error_msg = Msg("system", str(e), "system")
|
||||
self.speak(error_msg)
|
||||
self.memory.add(error_msg)
|
||||
|
||||
# Exceed the maximum iterations
|
||||
hint_msg = Msg(
|
||||
"system",
|
||||
"You have failed to generate a response in the maximum "
|
||||
"iterations. Now generate a reply by summarizing the current "
|
||||
"situation.",
|
||||
role="system",
|
||||
echo=self.verbose,
|
||||
)
|
||||
|
||||
# Generate a reply by summarizing the current situation
|
||||
prompt = self.model.format(self.memory.get_memory(), hint_msg)
|
||||
res = self.model(prompt)
|
||||
res_msg = Msg(self.name, res.text, "assistant")
|
||||
self.speak(res_msg)
|
||||
|
||||
return res_msg
|
||||
178
AlgoriAgent/src/agentscope/agents/rpc_agent.py
Normal file
178
AlgoriAgent/src/agentscope/agents/rpc_agent.py
Normal file
@@ -0,0 +1,178 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Base class for Rpc Agent """
|
||||
from typing import Type, Optional, Union, Sequence
|
||||
|
||||
from agentscope.agents.agent import AgentBase
|
||||
from agentscope.message import (
|
||||
PlaceholderMessage,
|
||||
serialize,
|
||||
Msg,
|
||||
)
|
||||
from agentscope.rpc import RpcAgentClient
|
||||
from agentscope.server.launcher import RpcAgentServerLauncher
|
||||
from agentscope.studio._client import _studio_client
|
||||
|
||||
|
||||
class RpcAgent(AgentBase):
|
||||
"""A wrapper to extend an AgentBase into a gRPC Client."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
host: str = "localhost",
|
||||
port: int = None,
|
||||
agent_class: Type[AgentBase] = None,
|
||||
agent_configs: Optional[dict] = None,
|
||||
max_pool_size: int = 8192,
|
||||
max_timeout_seconds: int = 1800,
|
||||
local_mode: bool = True,
|
||||
lazy_launch: bool = True,
|
||||
agent_id: str = None,
|
||||
connect_existing: bool = False,
|
||||
) -> None:
|
||||
"""Initialize a RpcAgent instance.
|
||||
|
||||
Args:
|
||||
name (`str`): the name of the agent.
|
||||
host (`str`, defaults to `localhost`):
|
||||
Hostname of the rpc agent server.
|
||||
port (`int`, defaults to `None`):
|
||||
Port of the rpc agent server.
|
||||
agent_class (`Type[AgentBase]`):
|
||||
the AgentBase subclass of the source agent.
|
||||
agent_configs (`dict`): The args used to
|
||||
initialize the agent, generated by `_AgentMeta`.
|
||||
max_pool_size (`int`, defaults to `8192`):
|
||||
Max number of task results that the server can accommodate.
|
||||
max_timeout_seconds (`int`, defaults to `1800`):
|
||||
Timeout for task results.
|
||||
local_mode (`bool`, defaults to `True`):
|
||||
Whether the started gRPC server only listens to local
|
||||
requests.
|
||||
lazy_launch (`bool`, defaults to `True`):
|
||||
Only launch the server when the agent is called.
|
||||
agent_id (`str`, defaults to `None`):
|
||||
The agent id of this instance. If `None`, it will
|
||||
be generated randomly.
|
||||
connect_existing (`bool`, defaults to `False`):
|
||||
Set to `True`, if the agent is already running on the agent
|
||||
server.
|
||||
"""
|
||||
super().__init__(name=name)
|
||||
self.agent_class = agent_class
|
||||
self.agent_configs = agent_configs
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.server_launcher = None
|
||||
self.client = None
|
||||
self.connect_existing = connect_existing
|
||||
if agent_id is not None:
|
||||
self._agent_id = agent_id
|
||||
# if host and port are not provided, launch server locally
|
||||
launch_server = port is None
|
||||
if launch_server:
|
||||
self.host = "localhost"
|
||||
studio_url = None
|
||||
if _studio_client.active:
|
||||
studio_url = _studio_client.studio_url
|
||||
self.server_launcher = RpcAgentServerLauncher(
|
||||
host=self.host,
|
||||
port=port,
|
||||
max_pool_size=max_pool_size,
|
||||
max_timeout_seconds=max_timeout_seconds,
|
||||
local_mode=local_mode,
|
||||
custom_agents=[agent_class],
|
||||
studio_url=studio_url,
|
||||
)
|
||||
if not lazy_launch:
|
||||
self._launch_server()
|
||||
else:
|
||||
self.client = RpcAgentClient(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
agent_id=self.agent_id,
|
||||
)
|
||||
if not self.connect_existing:
|
||||
self.client.create_agent(agent_configs)
|
||||
|
||||
def _launch_server(self) -> None:
|
||||
"""Launch a rpc server and update the port and the client"""
|
||||
self.server_launcher.launch()
|
||||
self.port = self.server_launcher.port
|
||||
self.client = RpcAgentClient(
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
agent_id=self.agent_id,
|
||||
)
|
||||
self.client.create_agent(self.agent_configs)
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
if self.client is None:
|
||||
self._launch_server()
|
||||
return PlaceholderMessage(
|
||||
name=self.name,
|
||||
content=None,
|
||||
client=self.client,
|
||||
x=x,
|
||||
)
|
||||
|
||||
def observe(self, x: Union[dict, Sequence[dict]]) -> None:
|
||||
if self.client is None:
|
||||
self._launch_server()
|
||||
self.client.call_func(
|
||||
func_name="_observe",
|
||||
value=serialize(x), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
def clone_instances(
|
||||
self,
|
||||
num_instances: int,
|
||||
including_self: bool = True,
|
||||
) -> Sequence[AgentBase]:
|
||||
"""
|
||||
Clone a series of this instance with different agent_id and
|
||||
return them as a list.
|
||||
|
||||
Args:
|
||||
num_instances (`int`): The number of instances in the returned
|
||||
list.
|
||||
including_self (`bool`): Whether to include the instance calling
|
||||
this method in the returned list.
|
||||
|
||||
Returns:
|
||||
`Sequence[AgentBase]`: A list of agent instances.
|
||||
"""
|
||||
generated_instance_number = (
|
||||
num_instances - 1 if including_self else num_instances
|
||||
)
|
||||
generated_instances = []
|
||||
|
||||
# launch the server before clone instances
|
||||
if self.client is None:
|
||||
self._launch_server()
|
||||
|
||||
# put itself as the first element of the returned list
|
||||
if including_self:
|
||||
generated_instances.append(self)
|
||||
|
||||
# clone instances without agent server
|
||||
for _ in range(generated_instance_number):
|
||||
new_agent_id = self.client.call_func("_clone_agent")
|
||||
generated_instances.append(
|
||||
RpcAgent(
|
||||
name=self.name,
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
agent_id=new_agent_id,
|
||||
connect_existing=True,
|
||||
),
|
||||
)
|
||||
return generated_instances
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop the RpcAgent and the rpc server."""
|
||||
if self.server_launcher is not None:
|
||||
self.server_launcher.shutdown()
|
||||
|
||||
def __del__(self) -> None:
|
||||
self.stop()
|
||||
79
AlgoriAgent/src/agentscope/agents/text_to_image_agent.py
Normal file
79
AlgoriAgent/src/agentscope/agents/text_to_image_agent.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""An agent that convert text to image."""
|
||||
|
||||
from typing import Optional, Union, Sequence
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from .agent import AgentBase
|
||||
from ..message import Msg
|
||||
|
||||
|
||||
class TextToImageAgent(AgentBase):
|
||||
"""
|
||||
A agent used to perform text to image tasks.
|
||||
|
||||
TODO: change the agent into a service.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
model_config_name: str,
|
||||
use_memory: bool = True,
|
||||
memory_config: Optional[dict] = None,
|
||||
) -> None:
|
||||
"""Initialize the text to image agent.
|
||||
|
||||
Arguments:
|
||||
name (`str`):
|
||||
The name of the agent.
|
||||
model_config_name (`str`, defaults to None):
|
||||
The name of the model config, which is used to load model from
|
||||
configuration.
|
||||
use_memory (`bool`, defaults to `True`):
|
||||
Whether the agent has memory.
|
||||
memory_config (`Optional[dict]`):
|
||||
The config of memory.
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
sys_prompt="",
|
||||
model_config_name=model_config_name,
|
||||
use_memory=use_memory,
|
||||
memory_config=memory_config,
|
||||
)
|
||||
|
||||
logger.warning(
|
||||
"The `TextToImageAgent` will be deprecated in v0.0.6, "
|
||||
"please use `text_to_image` service and `ReActAgent` instead.",
|
||||
)
|
||||
|
||||
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
if x is None:
|
||||
# get the last message from memory
|
||||
if self.memory and self.memory.size() > 0:
|
||||
x = self.memory.get_memory()[-1]
|
||||
else:
|
||||
return Msg(
|
||||
self.name,
|
||||
content="Please provide a text prompt to generate image.",
|
||||
role="assistant",
|
||||
)
|
||||
image_urls = self.model(x.content).image_urls
|
||||
# TODO: optimize the construction of content
|
||||
msg = Msg(
|
||||
self.name,
|
||||
content="This is the generated image",
|
||||
role="assistant",
|
||||
url=image_urls,
|
||||
)
|
||||
|
||||
self.speak(msg)
|
||||
|
||||
if self.memory:
|
||||
self.memory.add(msg)
|
||||
|
||||
return msg
|
||||
150
AlgoriAgent/src/agentscope/agents/user_agent.py
Normal file
150
AlgoriAgent/src/agentscope/agents/user_agent.py
Normal file
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""User Agent class"""
|
||||
import time
|
||||
from typing import Union, Sequence
|
||||
from typing import Optional
|
||||
from loguru import logger
|
||||
|
||||
from agentscope.agents import AgentBase
|
||||
from agentscope.studio._client import _studio_client
|
||||
from agentscope.message import Msg
|
||||
from agentscope.web.gradio.utils import user_input
|
||||
|
||||
|
||||
class UserAgent(AgentBase):
|
||||
"""User agent class"""
|
||||
|
||||
def __init__(self, name: str = "User", require_url: bool = False) -> None:
|
||||
"""Initialize a UserAgent object.
|
||||
|
||||
Arguments:
|
||||
name (`str`, defaults to `"User"`):
|
||||
The name of the agent. Defaults to "User".
|
||||
require_url (`bool`, defaults to `False`):
|
||||
Whether the agent requires user to input a URL. Defaults to
|
||||
False. The URL can lead to a website, a file,
|
||||
or a directory. It will be added into the generated message
|
||||
in field `url`.
|
||||
"""
|
||||
super().__init__(name=name)
|
||||
|
||||
self.name = name
|
||||
self.require_url = require_url
|
||||
|
||||
def reply(
|
||||
self,
|
||||
x: Optional[Union[Msg, Sequence[Msg]]] = None,
|
||||
required_keys: Optional[Union[list[str], str]] = None,
|
||||
timeout: Optional[int] = None,
|
||||
) -> Msg:
|
||||
"""
|
||||
Processes the input provided by the user and stores it in memory,
|
||||
potentially formatting it with additional provided details.
|
||||
|
||||
The method prompts the user for input, then optionally prompts for
|
||||
additional specifics based on the provided format keys. All
|
||||
information is encapsulated in a message object, which is then
|
||||
added to the object's memory.
|
||||
|
||||
Arguments:
|
||||
x (`Optional[Union[Msg, Sequence[Msg]]]`, defaults to `None`):
|
||||
The input message(s) to the agent, which also can be omitted if
|
||||
the agent doesn't need any input.
|
||||
required_keys \
|
||||
(`Optional[Union[list[str], str]]`, defaults to `None`):
|
||||
Strings that requires user to input, which will be used as
|
||||
the key of the returned dict. Defaults to None.
|
||||
timeout (`Optional[int]`, defaults to `None`):
|
||||
Raise `TimeoutError` if user exceed input time, set to None
|
||||
for no limit.
|
||||
|
||||
Returns:
|
||||
`Msg`: The output message generated by the agent.
|
||||
"""
|
||||
if self.memory:
|
||||
self.memory.add(x)
|
||||
|
||||
if _studio_client.active:
|
||||
logger.info(
|
||||
f"Waiting for input from:\n\n"
|
||||
f" * {_studio_client.get_run_detail_page_url()}\n",
|
||||
)
|
||||
raw_input = _studio_client.get_user_input(
|
||||
agent_id=self.agent_id,
|
||||
name=self.name,
|
||||
require_url=self.require_url,
|
||||
required_keys=required_keys,
|
||||
)
|
||||
|
||||
print("Python: receive ", raw_input)
|
||||
content = raw_input["content"]
|
||||
url = raw_input["url"]
|
||||
kwargs = {}
|
||||
else:
|
||||
# TODO: To avoid order confusion, because `input` print much
|
||||
# quicker than logger.chat
|
||||
time.sleep(0.5)
|
||||
content = user_input(timeout=timeout)
|
||||
kwargs = {}
|
||||
if required_keys is not None:
|
||||
if isinstance(required_keys, str):
|
||||
required_keys = [required_keys]
|
||||
|
||||
for key in required_keys:
|
||||
kwargs[key] = input(f"{key}: ")
|
||||
|
||||
# Input url of file, image, video, audio or website
|
||||
url = None
|
||||
if self.require_url:
|
||||
url = input("URL (or Enter to skip): ")
|
||||
if url == "":
|
||||
url = None
|
||||
|
||||
# Add additional keys
|
||||
msg = Msg(
|
||||
name=self.name,
|
||||
role="user",
|
||||
content=content,
|
||||
url=url,
|
||||
**kwargs, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
self.speak(msg)
|
||||
|
||||
# Add to memory
|
||||
if self.memory:
|
||||
self.memory.add(msg)
|
||||
|
||||
return msg
|
||||
|
||||
def speak(
|
||||
self,
|
||||
content: Union[str, Msg],
|
||||
) -> None:
|
||||
"""
|
||||
Speak out the message generated by the agent. If a string is given,
|
||||
a Msg object will be created with the string as the content.
|
||||
|
||||
Args:
|
||||
content (`Union[str, Msg]`):
|
||||
The content of the message to be spoken out. If a string is
|
||||
given, a Msg object will be created with the agent's name, role
|
||||
as "user", and the given string as the content.
|
||||
|
||||
"""
|
||||
if isinstance(content, str):
|
||||
msg = Msg(
|
||||
name=self.name,
|
||||
content=content,
|
||||
role="assistant",
|
||||
)
|
||||
_studio_client.push_message(msg)
|
||||
elif isinstance(content, Msg):
|
||||
msg = content
|
||||
else:
|
||||
raise TypeError(
|
||||
"From version 0.0.5, the speak method only accepts str or Msg "
|
||||
f"object, got {type(content)} instead.",
|
||||
)
|
||||
|
||||
logger.chat(msg, disable_gradio=True)
|
||||
67
AlgoriAgent/src/agentscope/constants.py
Normal file
67
AlgoriAgent/src/agentscope/constants.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Some constants used in the project"""
|
||||
from numbers import Number
|
||||
from enum import IntEnum
|
||||
|
||||
PACKAGE_NAME = "agentscope"
|
||||
MSG_TOKEN = f"[{PACKAGE_NAME}_msg]"
|
||||
|
||||
|
||||
# default values
|
||||
|
||||
# for file manager
|
||||
_DEFAULT_DIR = "./runs"
|
||||
_DEFAULT_LOG_LEVEL = "INFO"
|
||||
_DEFAULT_SUBDIR_CODE = "code"
|
||||
_DEFAULT_SUBDIR_FILE = "file"
|
||||
_DEFAULT_SUBDIR_INVOKE = "invoke"
|
||||
_DEFAULT_CFG_NAME = ".config"
|
||||
_DEFAULT_IMAGE_NAME = "image_{}_{}.png"
|
||||
_DEFAULT_SQLITE_DB_PATH = "agentscope.db"
|
||||
|
||||
|
||||
# for model wrapper
|
||||
_DEFAULT_MAX_RETRIES = 3
|
||||
_DEFAULT_MESSAGES_KEY = "inputs"
|
||||
_DEFAULT_RETRY_INTERVAL = 1
|
||||
_DEFAULT_API_BUDGET = None
|
||||
# for execute python
|
||||
_DEFAULT_PYPI_MIRROR = "http://mirrors.aliyun.com/pypi/simple/"
|
||||
_DEFAULT_TRUSTED_HOST = "mirrors.aliyun.com"
|
||||
# for monitor
|
||||
_DEFAULT_MONITOR_TABLE_NAME = "monitor_metrics"
|
||||
# for summarization
|
||||
_DEFAULT_SUMMARIZATION_PROMPT = """
|
||||
TEXT: {}
|
||||
"""
|
||||
_DEFAULT_SYSTEM_PROMPT = """
|
||||
You are a helpful agent to summarize the text.
|
||||
You need to keep all the key information of the text in the summary.
|
||||
"""
|
||||
_DEFAULT_TOKEN_LIMIT_PROMPT = """
|
||||
Summarize the text after TEXT in less than {} tokens:
|
||||
"""
|
||||
|
||||
# typing
|
||||
Embedding = list[Number]
|
||||
|
||||
|
||||
# enums
|
||||
class ResponseFormat(IntEnum):
|
||||
"""Enum for model response format."""
|
||||
|
||||
NONE = 0
|
||||
JSON = 1
|
||||
|
||||
|
||||
class ShrinkPolicy(IntEnum):
|
||||
"""Enum for shrink strategies when the prompt is too long."""
|
||||
|
||||
TRUNCATE = 0
|
||||
SUMMARIZE = 1
|
||||
|
||||
|
||||
# rag related
|
||||
DEFAULT_CHUNK_SIZE = 1024
|
||||
DEFAULT_CHUNK_OVERLAP = 20
|
||||
DEFAULT_TOP_K = 5
|
||||
113
AlgoriAgent/src/agentscope/exception.py
Normal file
113
AlgoriAgent/src/agentscope/exception.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""AgentScope exception classes."""
|
||||
|
||||
# - Model Response Parsing Exceptions
|
||||
|
||||
|
||||
class ResponseParsingError(Exception):
|
||||
"""The exception class for response parsing error with uncertain
|
||||
reasons."""
|
||||
|
||||
raw_response: str
|
||||
"""Record the raw response."""
|
||||
|
||||
def __init__(self, message: str, raw_response: str = None) -> None:
|
||||
"""Initialize the exception with the message."""
|
||||
self.message = message
|
||||
self.raw_response = raw_response
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__}: {self.message}"
|
||||
|
||||
|
||||
class JsonParsingError(ResponseParsingError):
|
||||
"""The exception class for JSON parsing error."""
|
||||
|
||||
|
||||
class JsonDictValidationError(ResponseParsingError):
|
||||
"""The exception class for JSON dict validation error."""
|
||||
|
||||
|
||||
class JsonTypeError(ResponseParsingError):
|
||||
"""The exception class for JSON type error."""
|
||||
|
||||
|
||||
class RequiredFieldNotFoundError(ResponseParsingError):
|
||||
"""The exception class for missing required field in model response, when
|
||||
the response is required to be a JSON dict object with required fields."""
|
||||
|
||||
|
||||
class TagNotFoundError(ResponseParsingError):
|
||||
"""The exception class for missing tagged content in model response."""
|
||||
|
||||
missing_begin_tag: bool
|
||||
"""If the response misses the begin tag."""
|
||||
|
||||
missing_end_tag: bool
|
||||
"""If the response misses the end tag."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
raw_response: str = None,
|
||||
missing_begin_tag: bool = True,
|
||||
missing_end_tag: bool = True,
|
||||
):
|
||||
"""Initialize the exception with the message.
|
||||
|
||||
Args:
|
||||
raw_response (`str`):
|
||||
Record the raw response from the model.
|
||||
missing_begin_tag (`bool`, defaults to `True`):
|
||||
If the response misses the beginning tag, default to `True`.
|
||||
missing_end_tag (`bool`, defaults to `True`):
|
||||
If the response misses the end tag, default to `True`.
|
||||
"""
|
||||
super().__init__(message, raw_response)
|
||||
|
||||
self.missing_begin_tag = missing_begin_tag
|
||||
self.missing_end_tag = missing_end_tag
|
||||
|
||||
|
||||
# - Function Calling Exceptions
|
||||
|
||||
|
||||
class FunctionCallError(Exception):
|
||||
"""The base class for exception raising during calling functions."""
|
||||
|
||||
def __init__(self, message: str) -> None:
|
||||
self.message = message
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__}: {self.message}"
|
||||
|
||||
|
||||
class FunctionCallFormatError(FunctionCallError):
|
||||
"""The exception class for function calling format error."""
|
||||
|
||||
|
||||
class FunctionNotFoundError(FunctionCallError):
|
||||
"""The exception class for function not found error."""
|
||||
|
||||
|
||||
class ArgumentNotFoundError(FunctionCallError):
|
||||
"""The exception class for missing argument error."""
|
||||
|
||||
|
||||
class ArgumentTypeError(FunctionCallError):
|
||||
"""The exception class for argument type error."""
|
||||
|
||||
|
||||
class StudioError(Exception):
|
||||
"""The base class for exception raising during interaction with agentscope
|
||||
studio."""
|
||||
|
||||
def __init__(self, message: str) -> None:
|
||||
self.message = message
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__}: {self.message}"
|
||||
|
||||
|
||||
class StudioRegisterError(StudioError):
|
||||
"""The exception class for error when registering to agentscope studio."""
|
||||
260
AlgoriAgent/src/agentscope/file_manager.py
Normal file
260
AlgoriAgent/src/agentscope/file_manager.py
Normal file
@@ -0,0 +1,260 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Manage the file system for saving files, code and logs."""
|
||||
import json
|
||||
import os
|
||||
from typing import Any, Union, Optional, List, Literal
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
|
||||
from agentscope._runtime import _runtime
|
||||
from agentscope.utils.tools import _download_file, _get_timestamp, _hash_string
|
||||
from agentscope.utils.tools import _generate_random_code
|
||||
from agentscope.constants import (
|
||||
_DEFAULT_DIR,
|
||||
_DEFAULT_SUBDIR_CODE,
|
||||
_DEFAULT_SUBDIR_FILE,
|
||||
_DEFAULT_SUBDIR_INVOKE,
|
||||
_DEFAULT_SQLITE_DB_PATH,
|
||||
_DEFAULT_IMAGE_NAME,
|
||||
_DEFAULT_CFG_NAME,
|
||||
)
|
||||
|
||||
|
||||
def _get_text_embedding_record_hash(
|
||||
text: str,
|
||||
embedding_model: Optional[Union[str, dict]],
|
||||
hash_method: Literal["sha256", "md5", "sha1"] = "sha256",
|
||||
) -> str:
|
||||
"""Get the hash of the text embedding record."""
|
||||
original_data_hash = _hash_string(text, hash_method)
|
||||
|
||||
if isinstance(embedding_model, dict):
|
||||
# Format the dict to avoid duplicate keys
|
||||
embedding_model = json.dumps(embedding_model, sort_keys=True)
|
||||
embedding_model_hash = _hash_string(embedding_model, hash_method)
|
||||
|
||||
# Calculate the embedding id by hashing the hash codes of the
|
||||
# original data and the embedding model
|
||||
record_hash = _hash_string(
|
||||
original_data_hash + embedding_model_hash,
|
||||
hash_method,
|
||||
)
|
||||
|
||||
return record_hash
|
||||
|
||||
|
||||
class _FileManager:
|
||||
"""A singleton class for managing the file system for saving files,
|
||||
code and logs."""
|
||||
|
||||
_instance = None
|
||||
|
||||
cache_dir: str = str(Path.home() / ".cache" / "agentscope")
|
||||
|
||||
hash_method: Literal["sha256", "md5", "sha1"] = "sha256"
|
||||
|
||||
dir: str = os.path.abspath(_DEFAULT_DIR)
|
||||
"""The directory for saving files, code and logs."""
|
||||
|
||||
save_api_invoke: bool = False
|
||||
"""Whether to save api invocation locally."""
|
||||
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Any:
|
||||
"""Create a singleton instance."""
|
||||
if not cls._instance:
|
||||
cls._instance = super(_FileManager, cls).__new__(
|
||||
cls,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
return cls._instance
|
||||
|
||||
def _get_and_create_subdir(self, subdir: str) -> str:
|
||||
"""Get the path of the subdir and create the subdir if it does not
|
||||
exist."""
|
||||
path = os.path.join(self.dir, _runtime.runtime_id, subdir)
|
||||
os.makedirs(path, exist_ok=True)
|
||||
return path
|
||||
|
||||
def _get_file_path(self, file_name: str) -> str:
|
||||
"""Get the path of the file."""
|
||||
return os.path.join(self.dir, _runtime.runtime_id, file_name)
|
||||
|
||||
@property
|
||||
def dir_cache(self) -> str:
|
||||
"""The directory for saving cache files."""
|
||||
return self.cache_dir
|
||||
|
||||
@property
|
||||
def dir_cache_embedding(self) -> str:
|
||||
"""Obtain the embedding cache directory."""
|
||||
dir_cache_embedding = os.path.join(self.cache_dir, "embedding")
|
||||
if not os.path.exists(dir_cache_embedding):
|
||||
os.makedirs(dir_cache_embedding)
|
||||
return dir_cache_embedding
|
||||
|
||||
@property
|
||||
def dir_root(self) -> str:
|
||||
"""The root directory to save code, information and logs."""
|
||||
return os.path.join(self.dir, _runtime.runtime_id)
|
||||
|
||||
@property
|
||||
def dir_log(self) -> str:
|
||||
"""The directory for saving logs."""
|
||||
return os.path.join(self.dir, _runtime.runtime_id)
|
||||
|
||||
@property
|
||||
def dir_file(self) -> str:
|
||||
"""The directory for saving files, including images, audios and
|
||||
videos."""
|
||||
return self._get_and_create_subdir(_DEFAULT_SUBDIR_FILE)
|
||||
|
||||
@property
|
||||
def dir_code(self) -> str:
|
||||
"""The directory for saving codes."""
|
||||
return self._get_and_create_subdir(_DEFAULT_SUBDIR_CODE)
|
||||
|
||||
@property
|
||||
def dir_invoke(self) -> str:
|
||||
"""The directory for saving api invocations."""
|
||||
return self._get_and_create_subdir(_DEFAULT_SUBDIR_INVOKE)
|
||||
|
||||
@property
|
||||
def path_db(self) -> str:
|
||||
"""The path to the sqlite db file."""
|
||||
return self._get_file_path(_DEFAULT_SQLITE_DB_PATH)
|
||||
|
||||
def init(self, save_dir: str, save_api_invoke: bool = False) -> None:
|
||||
"""Set the directory for saving files."""
|
||||
self.dir = os.path.abspath(save_dir)
|
||||
runtime_dir = os.path.join(save_dir, _runtime.runtime_id)
|
||||
os.makedirs(runtime_dir, exist_ok=True)
|
||||
|
||||
self.save_api_invoke = save_api_invoke
|
||||
|
||||
# Save the project and name to the runtime directory
|
||||
self._save_config()
|
||||
|
||||
def _save_config(self) -> None:
|
||||
"""Save the configuration of the runtime in its root directory."""
|
||||
cfg = {
|
||||
"project": _runtime.project,
|
||||
"name": _runtime.name,
|
||||
"run_id": _runtime.runtime_id,
|
||||
"timestamp": _runtime.timestamp,
|
||||
"pid": os.getpid(),
|
||||
}
|
||||
with open(
|
||||
os.path.join(self.dir_root, _DEFAULT_CFG_NAME),
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as file:
|
||||
json.dump(cfg, file, indent=4)
|
||||
|
||||
def save_api_invocation(
|
||||
self,
|
||||
prefix: str,
|
||||
record: dict,
|
||||
) -> Union[None, str]:
|
||||
"""Save api invocation locally."""
|
||||
if self.save_api_invoke:
|
||||
filename = f"{prefix}_{_generate_random_code()}.json"
|
||||
path_save = os.path.join(str(self.dir_invoke), filename)
|
||||
with open(path_save, "w", encoding="utf-8") as file:
|
||||
json.dump(record, file, indent=4)
|
||||
|
||||
return filename
|
||||
else:
|
||||
return None
|
||||
|
||||
def save_image(
|
||||
self,
|
||||
image: Union[str, np.ndarray],
|
||||
filename: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Save image file locally, and return the local image path.
|
||||
|
||||
Args:
|
||||
image (`Union[str, np.ndarray]`):
|
||||
The image url, or the image array.
|
||||
filename (`Optional[str]`):
|
||||
The filename of the image. If not specified, a random filename
|
||||
will be used.
|
||||
"""
|
||||
|
||||
if filename is None:
|
||||
filename = _DEFAULT_IMAGE_NAME.format(
|
||||
_get_timestamp(
|
||||
"%Y%m%d-%H%M%S",
|
||||
),
|
||||
_generate_random_code(),
|
||||
)
|
||||
|
||||
path_file = os.path.join(self.dir_file, filename)
|
||||
|
||||
if isinstance(image, str):
|
||||
# download the image from url
|
||||
_download_file(image, path_file)
|
||||
else:
|
||||
from PIL import Image
|
||||
|
||||
# save image via PIL
|
||||
Image.fromarray(image).save(path_file)
|
||||
|
||||
return path_file
|
||||
|
||||
def cache_text_embedding(
|
||||
self,
|
||||
text: str,
|
||||
embedding: List[float],
|
||||
embedding_model: Union[str, dict],
|
||||
) -> None:
|
||||
"""Cache the text embedding locally."""
|
||||
record_hash = _get_text_embedding_record_hash(
|
||||
text,
|
||||
embedding_model,
|
||||
self.hash_method,
|
||||
)
|
||||
|
||||
# Save the embedding to the cache directory
|
||||
np.save(
|
||||
os.path.join(
|
||||
self.dir_cache_embedding,
|
||||
f"{record_hash}.npy",
|
||||
),
|
||||
embedding,
|
||||
)
|
||||
|
||||
def fetch_cached_text_embedding(
|
||||
self,
|
||||
text: str,
|
||||
embedding_model: Union[str, dict],
|
||||
) -> Union[None, List[float]]:
|
||||
"""Fetch the text embedding from the cache."""
|
||||
record_hash = _get_text_embedding_record_hash(
|
||||
text,
|
||||
embedding_model,
|
||||
self.hash_method,
|
||||
)
|
||||
|
||||
try:
|
||||
return np.load(
|
||||
os.path.join(
|
||||
self.dir_cache_embedding,
|
||||
f"{record_hash}.npy",
|
||||
),
|
||||
)
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _flush() -> None:
|
||||
"""
|
||||
Only for unittest usage. Don't use this function in your code.
|
||||
Flush the file_manager singleton.
|
||||
"""
|
||||
global file_manager
|
||||
file_manager = _FileManager()
|
||||
|
||||
|
||||
file_manager = _FileManager()
|
||||
282
AlgoriAgent/src/agentscope/logging.py
Normal file
282
AlgoriAgent/src/agentscope/logging.py
Normal file
@@ -0,0 +1,282 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Logging utilities."""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional, Literal, Any
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from agentscope.studio._client import _studio_client
|
||||
from agentscope.web.gradio.utils import (
|
||||
generate_image_from_name,
|
||||
send_msg,
|
||||
get_reset_msg,
|
||||
thread_local_data,
|
||||
)
|
||||
|
||||
LOG_LEVEL = Literal[
|
||||
"TRACE",
|
||||
"DEBUG",
|
||||
"INFO",
|
||||
"SUCCESS",
|
||||
"WARNING",
|
||||
"ERROR",
|
||||
"CRITICAL",
|
||||
]
|
||||
|
||||
LEVEL_SAVE_LOG = "SAVE_LOG"
|
||||
LEVEL_SAVE_MSG = "SAVE_MSG"
|
||||
LEVEL_DISPLAY_MSG = "DISPLAY_MSG"
|
||||
|
||||
_SPEAKER_COLORS = [
|
||||
("\033[90m", "\033[0m"),
|
||||
("\033[91m", "\033[0m"),
|
||||
("\033[92m", "\033[0m"),
|
||||
("\033[93m", "\033[0m"),
|
||||
("\033[94m", "\033[0m"),
|
||||
("\033[95m", "\033[0m"),
|
||||
("\033[96m", "\033[0m"),
|
||||
("\033[97m", "\033[0m"),
|
||||
]
|
||||
|
||||
_SPEAKER_TO_COLORS = {}
|
||||
|
||||
|
||||
def _get_speaker_color(speaker: str) -> tuple[str, str]:
|
||||
"""Get the color markers for a speaker. If the speaker is new, assign a
|
||||
new color. Otherwise, return the color that was assigned to the speaker.
|
||||
|
||||
Args:
|
||||
speaker (`str`):
|
||||
The speaker to be assigned a color.
|
||||
|
||||
Returns:
|
||||
`tuple[str, str]`: A color marker tuple, e.g. ("<blue>", "</blue>").
|
||||
"""
|
||||
global _SPEAKER_COLORS, _SPEAKER_TO_COLORS
|
||||
if speaker in _SPEAKER_TO_COLORS:
|
||||
return _SPEAKER_TO_COLORS[speaker]
|
||||
else:
|
||||
markers = _SPEAKER_COLORS[
|
||||
(len(_SPEAKER_TO_COLORS) + 1)
|
||||
% len(
|
||||
_SPEAKER_COLORS,
|
||||
)
|
||||
]
|
||||
# Record the color for this speaker
|
||||
_SPEAKER_TO_COLORS[speaker] = markers
|
||||
return markers
|
||||
|
||||
|
||||
# add chat function for logger
|
||||
def _chat(
|
||||
message: dict,
|
||||
*args: Any,
|
||||
disable_gradio: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""
|
||||
Log a chat message with the format of"<speaker>: <content>". If the
|
||||
running instance is registered in the studio, the message will be sent
|
||||
and display in the studio.
|
||||
|
||||
Args:
|
||||
message (`dict`):
|
||||
The message to be logged as "<name/role>: <content>", which must
|
||||
be an object of Msg class.
|
||||
"""
|
||||
# Push message to studio if it is active
|
||||
if _studio_client.active:
|
||||
_studio_client.push_message(message)
|
||||
|
||||
# Save message into chat file, add default to ignore not serializable
|
||||
# objects
|
||||
logger.log(
|
||||
LEVEL_SAVE_MSG,
|
||||
json.dumps(message, ensure_ascii=False, default=lambda _: None),
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# Print message in terminal with specific format
|
||||
if isinstance(message, dict):
|
||||
contain_name_or_role = "name" in message or "role" in message
|
||||
contain_content = "content" in message
|
||||
contain_url = message.get("url", None) is not None
|
||||
|
||||
# print content if contain name or role and contain content
|
||||
if contain_name_or_role:
|
||||
speaker = message.get("name", None) or message.get("role", None)
|
||||
(m1, m2) = _get_speaker_color(speaker)
|
||||
|
||||
print_str = []
|
||||
print_str_without_markers = []
|
||||
if contain_content:
|
||||
print_str.append(
|
||||
f"{m1}\033[1m{speaker}\033[0m{m2}: {message['content']}",
|
||||
)
|
||||
print_str_without_markers.append(
|
||||
f"{speaker}: {message['content']}",
|
||||
)
|
||||
|
||||
if contain_url:
|
||||
print_str.append(
|
||||
f"{m1}\033[1m{speaker}\033[0m{m2}: {message['url']}",
|
||||
)
|
||||
print_str_without_markers.append(
|
||||
f"{speaker}: {message['url']}",
|
||||
)
|
||||
|
||||
if len(print_str) > 0:
|
||||
print_str = "\n".join(print_str)
|
||||
print_str_without_markers = "\n".join(
|
||||
print_str_without_markers,
|
||||
)
|
||||
|
||||
logger.log(LEVEL_DISPLAY_MSG, print_str, *args, **kwargs)
|
||||
logger.log(
|
||||
LEVEL_SAVE_LOG,
|
||||
print_str_without_markers,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
if hasattr(thread_local_data, "uid") and not disable_gradio:
|
||||
log_gradio(message, thread_local_data.uid, **kwargs)
|
||||
return
|
||||
|
||||
logger.log(LEVEL_DISPLAY_MSG, message, *args, **kwargs)
|
||||
logger.log(LEVEL_SAVE_LOG, message, *args, **kwargs)
|
||||
|
||||
|
||||
def log_gradio(message: dict, uid: str, **kwargs: Any) -> None:
|
||||
"""Send chat message to studio.
|
||||
|
||||
Args:
|
||||
message (`dict`):
|
||||
The message to be logged. It should have "name"(or "role") and
|
||||
"content" keys, and the message will be logged as "<name/role>:
|
||||
<content>".
|
||||
uid (`str`):
|
||||
The local value 'uid' of the thread.
|
||||
"""
|
||||
if uid:
|
||||
get_reset_msg(uid=uid)
|
||||
name = message.get("name", "default") or message.get("role", "default")
|
||||
avatar = kwargs.get("avatar", None) or generate_image_from_name(
|
||||
message["name"],
|
||||
)
|
||||
|
||||
msg = message["content"]
|
||||
flushing = True
|
||||
if "url" in message and message["url"]:
|
||||
flushing = False
|
||||
if isinstance(message["url"], str):
|
||||
message["url"] = [message["url"]]
|
||||
for i in range(len(message["url"])):
|
||||
msg += "\n" + f"""<img src="{message['url'][i]}"/>"""
|
||||
if "audio_path" in message and message["audio_path"]:
|
||||
flushing = False
|
||||
if isinstance(message["audio_path"], str):
|
||||
message["audio_path"] = [message["audio_path"]]
|
||||
for i in range(len(message["audio_path"])):
|
||||
msg += (
|
||||
"\n"
|
||||
+ f"""<audio src="{message['audio_path'][i]}"
|
||||
controls/></audio>"""
|
||||
)
|
||||
if "video_path" in message and message["video_path"]:
|
||||
flushing = False
|
||||
if isinstance(message["video_path"], str):
|
||||
message["video_path"] = [message["video_path"]]
|
||||
for i in range(len(message["video_path"])):
|
||||
msg += (
|
||||
"\n"
|
||||
+ f"""<video src="{message['video_path'][i]}"
|
||||
controls/></video>"""
|
||||
)
|
||||
|
||||
send_msg(
|
||||
msg,
|
||||
role=name,
|
||||
uid=uid,
|
||||
flushing=flushing,
|
||||
avatar=avatar,
|
||||
)
|
||||
|
||||
|
||||
def _level_format(record: dict) -> str:
|
||||
"""Format the log record."""
|
||||
# Display the chat message
|
||||
if record["level"].name in [LEVEL_DISPLAY_MSG, LEVEL_SAVE_LOG]:
|
||||
return "{message}\n"
|
||||
else:
|
||||
return (
|
||||
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{"
|
||||
"level: <8}</level> | <cyan>{name}</cyan>:<cyan>{"
|
||||
"function}</cyan>:<cyan>{line}</cyan> - <level>{"
|
||||
"message}</level>\n"
|
||||
)
|
||||
|
||||
|
||||
def setup_logger(
|
||||
path_log: Optional[str] = None,
|
||||
level: LOG_LEVEL = "INFO",
|
||||
) -> None:
|
||||
r"""Setup `loguru.logger` and redirect stderr to logging.
|
||||
|
||||
Args:
|
||||
path_log (`str`, defaults to `""`):
|
||||
The directory of log files.
|
||||
level (`str`, defaults to `"INFO"`):
|
||||
The logging level, which is one of the following: `"TRACE"`,
|
||||
`"DEBUG"`, `"INFO"`, `"SUCCESS"`, `"WARNING"`, `"ERROR"`,
|
||||
`"CRITICAL"`.
|
||||
"""
|
||||
# avoid reinit in subprocess
|
||||
if not hasattr(logger, "chat"):
|
||||
# add chat function for logger
|
||||
logger.level(LEVEL_SAVE_LOG, no=51)
|
||||
logger.level(LEVEL_DISPLAY_MSG, no=52)
|
||||
|
||||
# save chat message into file
|
||||
logger.level(LEVEL_SAVE_MSG, no=53)
|
||||
logger.chat = _chat
|
||||
|
||||
# set logging level
|
||||
logger.remove()
|
||||
# standard output for all logging except chat
|
||||
logger.add(
|
||||
sys.stdout,
|
||||
filter=lambda record: record["level"].name
|
||||
not in [LEVEL_SAVE_LOG, LEVEL_SAVE_MSG],
|
||||
format=_level_format,
|
||||
enqueue=True,
|
||||
level=level,
|
||||
)
|
||||
|
||||
if path_log is not None:
|
||||
os.makedirs(path_log, exist_ok=True)
|
||||
path_log_file = os.path.join(path_log, "logging.log")
|
||||
path_chat_file = os.path.join(
|
||||
path_log,
|
||||
"logging.chat",
|
||||
)
|
||||
|
||||
# save all logging into file
|
||||
logger.add(
|
||||
path_log_file,
|
||||
filter=lambda record: record["level"].name
|
||||
not in [LEVEL_SAVE_MSG, LEVEL_DISPLAY_MSG],
|
||||
format=_level_format,
|
||||
enqueue=True,
|
||||
level=level,
|
||||
)
|
||||
|
||||
logger.add(
|
||||
path_chat_file,
|
||||
format="{message}",
|
||||
enqueue=True,
|
||||
level=LEVEL_SAVE_MSG,
|
||||
)
|
||||
13
AlgoriAgent/src/agentscope/memory/__init__.py
Normal file
13
AlgoriAgent/src/agentscope/memory/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
import al memory related modules
|
||||
"""
|
||||
|
||||
from .memory import MemoryBase
|
||||
from .temporary_memory import TemporaryMemory
|
||||
|
||||
__all__ = [
|
||||
"MemoryBase",
|
||||
"TemporaryMemory",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
129
AlgoriAgent/src/agentscope/memory/memory.py
Normal file
129
AlgoriAgent/src/agentscope/memory/memory.py
Normal file
@@ -0,0 +1,129 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Base class for memory
|
||||
|
||||
TODO: a abstract class for a piece of memory
|
||||
TODO: data structure to organize multiple memory pieces in memory class
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Iterable, Sequence
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
from typing import Callable
|
||||
|
||||
from ..message import Msg
|
||||
|
||||
|
||||
class MemoryBase(ABC):
|
||||
"""Base class for memory."""
|
||||
|
||||
_version: int = 1
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Optional[dict] = None,
|
||||
) -> None:
|
||||
"""MemoryBase is a base class for memory of agents.
|
||||
|
||||
Args:
|
||||
config (`Optional[dict]`, defaults to `None`):
|
||||
Configuration of this memory.
|
||||
"""
|
||||
self.config = {} if config is None else config
|
||||
|
||||
def update_config(self, config: dict) -> None:
|
||||
"""
|
||||
Configure memory as specified in config
|
||||
Args:
|
||||
config (`dict`): Configuration of resetting this memory
|
||||
"""
|
||||
self.config = config
|
||||
|
||||
@abstractmethod
|
||||
def get_memory(
|
||||
self,
|
||||
recent_n: Optional[int] = None,
|
||||
filter_func: Optional[Callable[[int, dict], bool]] = None,
|
||||
) -> list:
|
||||
"""
|
||||
Return a certain range (`recent_n` or all) of memory,
|
||||
filtered by `filter_func`
|
||||
Args:
|
||||
recent_n (int, optional):
|
||||
indicate the most recent N memory pieces to be returned.
|
||||
filter_func (Optional[Callable[[int, dict], bool]]):
|
||||
filter function to decide which pieces of memory should
|
||||
be returned, taking the index and a piece of memory as
|
||||
input and return True (return this memory) or False
|
||||
(does not return)
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def add(
|
||||
self,
|
||||
memories: Union[Sequence[Msg], Msg, None],
|
||||
) -> None:
|
||||
"""
|
||||
Adding new memory fragment, depending on how the memory are stored
|
||||
Args:
|
||||
memories (Union[Sequence[Msg], Msg, None]):
|
||||
Memories to be added.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def delete(self, index: Union[Iterable, int]) -> None:
|
||||
"""
|
||||
Delete memory fragment, depending on how the memory are stored
|
||||
and matched
|
||||
Args:
|
||||
index (Union[Iterable, int]):
|
||||
indices of the memory fragments to delete
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def load(
|
||||
self,
|
||||
memories: Union[str, list[Msg], Msg],
|
||||
overwrite: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Load memory, depending on how the memory are passed, design to load
|
||||
from both file or dict
|
||||
Args:
|
||||
memories (Union[str, list[Msg], Msg]):
|
||||
memories to be loaded.
|
||||
If it is in str type, it will be first checked if it is a
|
||||
file; otherwise it will be deserialized as messages.
|
||||
Otherwise, memories must be either in message type or list
|
||||
of messages.
|
||||
overwrite (bool):
|
||||
if True, clear the current memory before loading the new ones;
|
||||
if False, memories will be appended to the old one at the end.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def export(
|
||||
self,
|
||||
file_path: Optional[str] = None,
|
||||
to_mem: bool = False,
|
||||
) -> Optional[list]:
|
||||
"""
|
||||
Export memory, depending on how the memory are stored
|
||||
Args:
|
||||
file_path (Optional[str]):
|
||||
file path to save the memory to.
|
||||
to_mem (Optional[str]):
|
||||
if True, just return the list of messages in memory
|
||||
Notice: this method prevents file_path is None when to_mem
|
||||
is False.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def clear(self) -> None:
|
||||
"""Clean memory, depending on how the memory are stored"""
|
||||
|
||||
@abstractmethod
|
||||
def size(self) -> int:
|
||||
"""Returns the number of memory segments in memory."""
|
||||
raise NotImplementedError
|
||||
350
AlgoriAgent/src/agentscope/memory/temporary_memory.py
Normal file
350
AlgoriAgent/src/agentscope/memory/temporary_memory.py
Normal file
@@ -0,0 +1,350 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Memory module for conversation
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Iterable, Sequence
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
from typing import Callable
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from .memory import MemoryBase
|
||||
from ..models import load_model_by_config_name
|
||||
from ..service.retrieval.retrieval_from_list import retrieve_from_list
|
||||
from ..service.retrieval.similarity import Embedding
|
||||
from ..message import (
|
||||
deserialize,
|
||||
serialize,
|
||||
MessageBase,
|
||||
Msg,
|
||||
PlaceholderMessage,
|
||||
)
|
||||
|
||||
|
||||
class TemporaryMemory(MemoryBase):
|
||||
"""
|
||||
In-memory memory module, not writing to hard disk
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Optional[dict] = None,
|
||||
embedding_model: Union[str, Callable] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Temporary memory module for conversation.
|
||||
Args:
|
||||
config (dict):
|
||||
configuration of the memory
|
||||
embedding_model (Union[str, Callable])
|
||||
if the temporary memory needs to be embedded,
|
||||
then either pass the name of embedding model or
|
||||
the embedding model itself.
|
||||
"""
|
||||
super().__init__(config)
|
||||
|
||||
self._content = []
|
||||
|
||||
# prepare embedding model if needed
|
||||
if isinstance(embedding_model, str):
|
||||
self.embedding_model = load_model_by_config_name(embedding_model)
|
||||
else:
|
||||
self.embedding_model = embedding_model
|
||||
|
||||
def add(
|
||||
self,
|
||||
memories: Union[Sequence[Msg], Msg, None],
|
||||
embed: bool = False,
|
||||
) -> None:
|
||||
# pylint: disable=too-many-branches
|
||||
"""
|
||||
Adding new memory fragment, depending on how the memory are stored
|
||||
Args:
|
||||
memories (`Union[Sequence[Msg], Msg, None]`):
|
||||
Memories to be added.
|
||||
embed (`bool`):
|
||||
Whether to generate embedding for the new added memories
|
||||
"""
|
||||
if memories is None:
|
||||
return
|
||||
|
||||
if not isinstance(memories, Sequence):
|
||||
record_memories = [memories]
|
||||
else:
|
||||
record_memories = memories
|
||||
|
||||
# if memory doesn't have id attribute, we skip the checking
|
||||
memories_idx = set(_.id for _ in self._content if hasattr(_, "id"))
|
||||
for memory_unit in record_memories:
|
||||
if not issubclass(type(memory_unit), MessageBase):
|
||||
try:
|
||||
memory_unit = Msg(**memory_unit)
|
||||
except Exception as exc:
|
||||
raise ValueError(
|
||||
f"Cannot add {memory_unit} to memory, "
|
||||
f"must be with subclass of MessageBase",
|
||||
) from exc
|
||||
|
||||
# in case this is a PlaceholderMessage, try to update
|
||||
# the values first
|
||||
if isinstance(memory_unit, PlaceholderMessage):
|
||||
memory_unit.update_value()
|
||||
memory_unit = Msg(**memory_unit)
|
||||
|
||||
# add to memory if it's new
|
||||
if (
|
||||
not hasattr(memory_unit, "id")
|
||||
or memory_unit.id not in memories_idx
|
||||
):
|
||||
if embed:
|
||||
if self.embedding_model:
|
||||
# TODO: embed only content or its string representation
|
||||
memory_unit.embedding = self.embedding_model(
|
||||
[memory_unit],
|
||||
return_embedding_only=True,
|
||||
)
|
||||
else:
|
||||
raise RuntimeError("Embedding model is not provided.")
|
||||
self._content.append(memory_unit)
|
||||
|
||||
def delete(self, index: Union[Iterable, int]) -> None:
|
||||
"""
|
||||
Delete memory fragment, depending on how the memory are stored
|
||||
and matched
|
||||
Args:
|
||||
index (Union[Iterable, int]):
|
||||
indices of the memory fragments to delete
|
||||
"""
|
||||
if self.size() == 0:
|
||||
logger.warning(
|
||||
"The memory is empty, and the delete operation is "
|
||||
"skipping.",
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(index, int):
|
||||
index = [index]
|
||||
|
||||
if isinstance(index, list):
|
||||
index = set(index)
|
||||
|
||||
invalid_index = [_ for _ in index if _ >= self.size() or _ < 0]
|
||||
if len(invalid_index) > 0:
|
||||
logger.warning(
|
||||
f"Skip delete operation for the invalid "
|
||||
f"index {invalid_index}",
|
||||
)
|
||||
|
||||
self._content = [
|
||||
_ for i, _ in enumerate(self._content) if i not in index
|
||||
]
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"index type only supports {None, int, list}",
|
||||
)
|
||||
|
||||
def export(
|
||||
self,
|
||||
file_path: Optional[str] = None,
|
||||
to_mem: bool = False,
|
||||
) -> Optional[list]:
|
||||
"""
|
||||
Export memory, depending on how the memory are stored
|
||||
Args:
|
||||
file_path (Optional[str]):
|
||||
file path to save the memory to. The messages will
|
||||
be serialized and written to the file.
|
||||
to_mem (Optional[str]):
|
||||
if True, just return the list of messages in memory
|
||||
Notice: this method prevents file_path is None when to_mem
|
||||
is False.
|
||||
"""
|
||||
if to_mem:
|
||||
return self._content
|
||||
|
||||
if to_mem is False and file_path is not None:
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.write(serialize(self._content))
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"file type only supports "
|
||||
"{json, yaml, pkl}, default is json",
|
||||
)
|
||||
return None
|
||||
|
||||
def load(
|
||||
self,
|
||||
memories: Union[str, list[Msg], Msg],
|
||||
overwrite: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Load memory, depending on how the memory are passed, design to load
|
||||
from both file or dict
|
||||
Args:
|
||||
memories (Union[str, list[Msg], Msg]):
|
||||
memories to be loaded.
|
||||
If it is in str type, it will be first checked if it is a
|
||||
file; otherwise it will be deserialized as messages.
|
||||
Otherwise, memories must be either in message type or list
|
||||
of messages.
|
||||
overwrite (bool):
|
||||
if True, clear the current memory before loading the new ones;
|
||||
if False, memories will be appended to the old one at the end.
|
||||
"""
|
||||
if isinstance(memories, str):
|
||||
if os.path.isfile(memories):
|
||||
with open(memories, "r", encoding="utf-8") as f:
|
||||
load_memories = deserialize(f.read())
|
||||
else:
|
||||
try:
|
||||
load_memories = deserialize(memories)
|
||||
if not isinstance(load_memories, dict) and not isinstance(
|
||||
load_memories,
|
||||
list,
|
||||
):
|
||||
logger.warning(
|
||||
"The memory loaded by json.loads is "
|
||||
"neither a dict nor a list, which may "
|
||||
"cause unpredictable errors.",
|
||||
)
|
||||
except json.JSONDecodeError as e:
|
||||
raise json.JSONDecodeError(
|
||||
f"Cannot load [{memories}] via " f"json.loads.",
|
||||
e.doc,
|
||||
e.pos,
|
||||
)
|
||||
else:
|
||||
load_memories = memories
|
||||
|
||||
# overwrite the original memories after loading the new ones
|
||||
if overwrite:
|
||||
self.clear()
|
||||
|
||||
self.add(load_memories)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clean memory, depending on how the memory are stored"""
|
||||
self._content = []
|
||||
|
||||
def size(self) -> int:
|
||||
"""Returns the number of memory segments in memory."""
|
||||
return len(self._content)
|
||||
|
||||
def retrieve_by_embedding(
|
||||
self,
|
||||
query: Union[str, Embedding],
|
||||
metric: Callable[[Embedding, Embedding], float],
|
||||
top_k: int = 1,
|
||||
preserve_order: bool = True,
|
||||
embedding_model: Callable[[Union[str, dict]], Embedding] = None,
|
||||
) -> list[dict]:
|
||||
"""Retrieve memory by their embeddings.
|
||||
|
||||
Args:
|
||||
query (`Union[str, Embedding]`):
|
||||
Query string or embedding.
|
||||
metric (`Callable[[Embedding, Embedding], float]`):
|
||||
A metric to compute the relevance between embeddings of query
|
||||
and memory. In default, higher relevance means better match,
|
||||
and you can set `reverse` to `True` to reverse the order.
|
||||
top_k (`int`, defaults to `1`):
|
||||
The number of memory units to retrieve.
|
||||
preserve_order (`bool`, defaults to `True`):
|
||||
Whether to preserve the original order of the retrieved memory
|
||||
units.
|
||||
embedding_model (`Callable[[Union[str, dict]], Embedding]`, \
|
||||
defaults to `None`):
|
||||
A callable object to embed the memory unit. If not provided, it
|
||||
will use the default embedding model.
|
||||
|
||||
Returns:
|
||||
`list[dict]`: a list of retrieved memory units in
|
||||
specific order.
|
||||
"""
|
||||
|
||||
retrieved_items = retrieve_from_list(
|
||||
query,
|
||||
self.get_embeddings(embedding_model or self.embedding_model),
|
||||
metric,
|
||||
top_k,
|
||||
self.embedding_model,
|
||||
preserve_order,
|
||||
).content
|
||||
|
||||
# obtain the corresponding memory item
|
||||
response = []
|
||||
for score, index, _ in retrieved_items:
|
||||
response.append(
|
||||
{
|
||||
"score": score,
|
||||
"index": index,
|
||||
"memory": self._content[index],
|
||||
},
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def get_embeddings(
|
||||
self,
|
||||
embedding_model: Callable[[Union[str, dict]], Embedding] = None,
|
||||
) -> list:
|
||||
"""Get embeddings of all memory units. If `embedding_model` is
|
||||
provided, the memory units that doesn't have `embedding` attribute
|
||||
will be embedded. Otherwise, its embedding will be `None`.
|
||||
|
||||
Args:
|
||||
embedding_model
|
||||
(`Callable[[Union[str, dict]], Embedding]`, defaults to
|
||||
`None`):
|
||||
Embedding model or embedding vector.
|
||||
|
||||
Returns:
|
||||
`list[Union[Embedding, None]]`: List of embeddings or None.
|
||||
"""
|
||||
embeddings = []
|
||||
for memory_unit in self._content:
|
||||
if memory_unit.embedding is None and embedding_model is not None:
|
||||
# embedding
|
||||
# TODO: embed only content or its string representation
|
||||
memory_unit.embedding = embedding_model(memory_unit)
|
||||
embeddings.append(memory_unit.embedding)
|
||||
return embeddings
|
||||
|
||||
def get_memory(
|
||||
self,
|
||||
recent_n: Optional[int] = None,
|
||||
filter_func: Optional[Callable[[int, dict], bool]] = None,
|
||||
) -> list:
|
||||
"""Retrieve memory.
|
||||
|
||||
Args:
|
||||
recent_n (`Optional[int]`, default `None`):
|
||||
The last number of memories to return.
|
||||
filter_func
|
||||
(`Callable[[int, dict], bool]`, default to `None`):
|
||||
The function to filter memories, which take the index and
|
||||
memory unit as input, and return a boolean value.
|
||||
"""
|
||||
# extract the recent `recent_n` entries in memories
|
||||
if recent_n is None:
|
||||
memories = self._content
|
||||
else:
|
||||
if recent_n > self.size():
|
||||
logger.warning(
|
||||
"The retrieved number of memories {} is "
|
||||
"greater than the total number of memories {"
|
||||
"}",
|
||||
recent_n,
|
||||
self.size(),
|
||||
)
|
||||
memories = self._content[-recent_n:]
|
||||
|
||||
# filter the memories
|
||||
if filter_func is not None:
|
||||
memories = [_ for i, _ in enumerate(memories) if filter_func(i, _)]
|
||||
|
||||
return memories
|
||||
377
AlgoriAgent/src/agentscope/message.py
Normal file
377
AlgoriAgent/src/agentscope/message.py
Normal file
@@ -0,0 +1,377 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""The base class for message unit"""
|
||||
|
||||
from typing import Any, Optional, Union, Sequence, Literal
|
||||
from uuid import uuid4
|
||||
import json
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from .rpc import RpcAgentClient, ResponseStub, call_in_thread
|
||||
from .utils.tools import _get_timestamp
|
||||
|
||||
|
||||
class MessageBase(dict):
|
||||
"""Base Message class, which is used to maintain information for dialog,
|
||||
memory and used to construct prompt.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
content: Any,
|
||||
role: Literal["user", "system", "assistant"] = "assistant",
|
||||
url: Optional[Union[Sequence[str], str]] = None,
|
||||
timestamp: Optional[str] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the message object
|
||||
|
||||
Args:
|
||||
name (`str`):
|
||||
The name of who send the message. It's often used in
|
||||
role-playing scenario to tell the name of the sender.
|
||||
content (`Any`):
|
||||
The content of the message.
|
||||
role (`Literal["system", "user", "assistant"]`, defaults to "assistant"):
|
||||
The role of who send the message. It can be one of the
|
||||
`"system"`, `"user"`, or `"assistant"`. Default to
|
||||
`"assistant"`.
|
||||
url (`Optional[Union[list[str], str]]`, defaults to None):
|
||||
A url to file, image, video, audio or website.
|
||||
timestamp (`Optional[str]`, defaults to None):
|
||||
The timestamp of the message, if None, it will be set to
|
||||
current time.
|
||||
**kwargs (`Any`):
|
||||
Other attributes of the message.
|
||||
""" # noqa
|
||||
# id and timestamp will be added to the object as its attributes
|
||||
# rather than items in dict
|
||||
self.id = uuid4().hex
|
||||
if timestamp is None:
|
||||
self.timestamp = _get_timestamp()
|
||||
else:
|
||||
self.timestamp = timestamp
|
||||
|
||||
self.name = name
|
||||
self.content = content
|
||||
self.role = role
|
||||
|
||||
self.url = url
|
||||
|
||||
self.update(kwargs)
|
||||
|
||||
def __getattr__(self, key: Any) -> Any:
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError as e:
|
||||
raise AttributeError(f"no attribute '{key}'") from e
|
||||
|
||||
def __setattr__(self, key: Any, value: Any) -> None:
|
||||
self[key] = value
|
||||
|
||||
def __delattr__(self, key: Any) -> None:
|
||||
try:
|
||||
del self[key]
|
||||
except KeyError as e:
|
||||
raise AttributeError(f"no attribute '{key}'") from e
|
||||
|
||||
def to_str(self) -> str:
|
||||
"""Return the string representation of the message"""
|
||||
raise NotImplementedError
|
||||
|
||||
def serialize(self) -> str:
|
||||
"""Return the serialized message."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Msg(MessageBase):
|
||||
"""The Message class."""
|
||||
|
||||
id: str
|
||||
"""The id of the message."""
|
||||
|
||||
name: str
|
||||
"""The name of who send the message."""
|
||||
|
||||
content: Any
|
||||
"""The content of the message."""
|
||||
|
||||
role: Literal["system", "user", "assistant"]
|
||||
"""The role of the message sender."""
|
||||
|
||||
metadata: Optional[dict]
|
||||
"""Save the information for application's control flow, or other
|
||||
purposes."""
|
||||
|
||||
url: Optional[Union[Sequence[str], str]]
|
||||
"""A url to file, image, video, audio or website."""
|
||||
|
||||
timestamp: str
|
||||
"""The timestamp of the message."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
content: Any,
|
||||
role: Literal["system", "user", "assistant"] = None,
|
||||
url: Optional[Union[Sequence[str], str]] = None,
|
||||
timestamp: Optional[str] = None,
|
||||
echo: bool = False,
|
||||
metadata: Optional[Union[dict, str]] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the message object
|
||||
|
||||
Args:
|
||||
name (`str`):
|
||||
The name of who send the message.
|
||||
content (`Any`):
|
||||
The content of the message.
|
||||
role (`Literal["system", "user", "assistant"]`):
|
||||
Used to identify the source of the message, e.g. the system
|
||||
information, the user input, or the model response. This
|
||||
argument is used to accommodate most Chat API formats.
|
||||
url (`Optional[Union[list[str], str]]`, defaults to `None`):
|
||||
A url to file, image, video, audio or website.
|
||||
timestamp (`Optional[str]`, defaults to `None`):
|
||||
The timestamp of the message, if None, it will be set to
|
||||
current time.
|
||||
echo (`bool`, defaults to `False`):
|
||||
Whether to print the message to the console.
|
||||
metadata (`Optional[Union[dict, str]]`, defaults to `None`):
|
||||
Save the information for application's control flow, or other
|
||||
purposes.
|
||||
**kwargs (`Any`):
|
||||
Other attributes of the message.
|
||||
"""
|
||||
|
||||
if role is None:
|
||||
logger.warning(
|
||||
"A new field `role` is newly added to the message. "
|
||||
"Please specify the role of the message. Currently we use "
|
||||
'a default "assistant" value.',
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
name=name,
|
||||
content=content,
|
||||
role=role or "assistant",
|
||||
url=url,
|
||||
timestamp=timestamp,
|
||||
metadata=metadata,
|
||||
**kwargs,
|
||||
)
|
||||
if echo:
|
||||
logger.chat(self)
|
||||
|
||||
def to_str(self) -> str:
|
||||
"""Return the string representation of the message"""
|
||||
return f"{self.name}: {self.content}"
|
||||
|
||||
def serialize(self) -> str:
|
||||
return json.dumps({"__type": "Msg", **self})
|
||||
#return json.dumps({"role" : self.role, "content": str(self.content)})
|
||||
|
||||
|
||||
class PlaceholderMessage(Msg):
|
||||
"""A placeholder for the return message of RpcAgent."""
|
||||
|
||||
PLACEHOLDER_ATTRS = {
|
||||
"_host",
|
||||
"_port",
|
||||
"_client",
|
||||
"_task_id",
|
||||
"_stub",
|
||||
"_is_placeholder",
|
||||
}
|
||||
|
||||
LOCAL_ATTRS = {
|
||||
"name",
|
||||
"timestamp",
|
||||
*PLACEHOLDER_ATTRS,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
content: Any,
|
||||
url: Optional[Union[Sequence[str], str]] = None,
|
||||
timestamp: Optional[str] = None,
|
||||
host: str = None,
|
||||
port: int = None,
|
||||
task_id: int = None,
|
||||
client: Optional[RpcAgentClient] = None,
|
||||
x: dict = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""A placeholder message, records the address of the real message.
|
||||
|
||||
Args:
|
||||
name (`str`):
|
||||
The name of who send the message. It's often used in
|
||||
role-playing scenario to tell the name of the sender.
|
||||
However, you can also only use `role` when calling openai api.
|
||||
The usage of `name` refers to
|
||||
https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models.
|
||||
content (`Any`):
|
||||
The content of the message.
|
||||
role (`Literal["system", "user", "assistant"]`, defaults to "assistant"):
|
||||
The role of the message, which can be one of the `"system"`,
|
||||
`"user"`, or `"assistant"`.
|
||||
url (`Optional[Union[list[str], str]]`, defaults to None):
|
||||
A url to file, image, video, audio or website.
|
||||
timestamp (`Optional[str]`, defaults to None):
|
||||
The timestamp of the message, if None, it will be set to
|
||||
current time.
|
||||
host (`str`, defaults to `None`):
|
||||
The hostname of the rpc server where the real message is
|
||||
located.
|
||||
port (`int`, defaults to `None`):
|
||||
The port of the rpc server where the real message is located.
|
||||
task_id (`int`, defaults to `None`):
|
||||
The task id of the real message in the rpc server.
|
||||
client (`RpcAgentClient`, defaults to `None`):
|
||||
An RpcAgentClient instance used to connect to the generator of
|
||||
this placeholder.
|
||||
x (`dict`, defaults to `None`):
|
||||
Input parameters used to call rpc methods on the client.
|
||||
""" # noqa
|
||||
super().__init__(
|
||||
name=name,
|
||||
content=content,
|
||||
url=url,
|
||||
timestamp=timestamp,
|
||||
**kwargs,
|
||||
)
|
||||
# placeholder indicates whether the real message is still in rpc server
|
||||
self._is_placeholder = True
|
||||
if client is None:
|
||||
self._stub: ResponseStub = None
|
||||
self._host: str = host
|
||||
self._port: int = port
|
||||
self._task_id: int = task_id
|
||||
else:
|
||||
self._stub = call_in_thread(
|
||||
client,
|
||||
x.serialize() if x is not None else "",
|
||||
"_reply",
|
||||
)
|
||||
self._host = client.host
|
||||
self._port = client.port
|
||||
self._task_id = None
|
||||
|
||||
def __is_local(self, key: Any) -> bool:
|
||||
return (
|
||||
key in PlaceholderMessage.LOCAL_ATTRS or not self._is_placeholder
|
||||
)
|
||||
|
||||
def __getattr__(self, __name: str) -> Any:
|
||||
"""Get attribute value from PlaceholderMessage. Get value from rpc
|
||||
agent server if necessary.
|
||||
|
||||
Args:
|
||||
__name (`str`):
|
||||
Attribute name.
|
||||
"""
|
||||
if not self.__is_local(__name):
|
||||
self.update_value()
|
||||
return MessageBase.__getattr__(self, __name)
|
||||
|
||||
def __getitem__(self, __key: Any) -> Any:
|
||||
"""Get item value from PlaceholderMessage. Get value from rpc
|
||||
agent server if necessary.
|
||||
|
||||
Args:
|
||||
__key (`Any`):
|
||||
Item name.
|
||||
"""
|
||||
if not self.__is_local(__key):
|
||||
self.update_value()
|
||||
return MessageBase.__getitem__(self, __key)
|
||||
|
||||
def to_str(self) -> str:
|
||||
return f"{self.name}: {self.content}"
|
||||
|
||||
def update_value(self) -> MessageBase:
|
||||
"""Get attribute values from rpc agent server immediately"""
|
||||
if self._is_placeholder:
|
||||
# retrieve real message from rpc agent server
|
||||
self.__update_task_id()
|
||||
client = RpcAgentClient(self._host, self._port)
|
||||
result = client.call_func(
|
||||
func_name="_get",
|
||||
value=json.dumps({"task_id": self._task_id}),
|
||||
)
|
||||
msg = deserialize(result)
|
||||
status = msg.pop("__status", "OK")
|
||||
if status == "ERROR":
|
||||
raise RuntimeError(msg.content)
|
||||
self.update(msg)
|
||||
# the actual value has been updated, not a placeholder anymore
|
||||
self._is_placeholder = False
|
||||
return self
|
||||
|
||||
def __update_task_id(self) -> None:
|
||||
if self._stub is not None:
|
||||
try:
|
||||
resp = deserialize(self._stub.get_response())
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Failed to get task_id: {self._stub.get_response()}",
|
||||
)
|
||||
raise ValueError(
|
||||
f"Failed to get task_id: {self._stub.get_response()}",
|
||||
) from e
|
||||
self._task_id = resp["task_id"] # type: ignore[call-overload]
|
||||
self._stub = None
|
||||
|
||||
def serialize(self) -> str:
|
||||
if self._is_placeholder:
|
||||
self.__update_task_id()
|
||||
return json.dumps(
|
||||
{
|
||||
"__type": "PlaceholderMessage",
|
||||
"name": self.name,
|
||||
"content": None,
|
||||
"timestamp": self.timestamp,
|
||||
"host": self._host,
|
||||
"port": self._port,
|
||||
"task_id": self._task_id,
|
||||
},
|
||||
)
|
||||
else:
|
||||
states = {
|
||||
k: v
|
||||
for k, v in self.items()
|
||||
if k not in PlaceholderMessage.PLACEHOLDER_ATTRS
|
||||
}
|
||||
states["__type"] = "Msg"
|
||||
return json.dumps(states)
|
||||
|
||||
|
||||
_MSGS = {
|
||||
"Msg": Msg,
|
||||
"PlaceholderMessage": PlaceholderMessage,
|
||||
}
|
||||
|
||||
|
||||
def deserialize(s: Union[str, bytes]) -> Union[Msg, Sequence]:
|
||||
"""Deserialize json string into MessageBase"""
|
||||
js_msg = json.loads(s)
|
||||
msg_type = js_msg.pop("__type")
|
||||
if msg_type == "List":
|
||||
return [deserialize(s) for s in js_msg["__value"]]
|
||||
elif msg_type not in _MSGS:
|
||||
raise NotImplementedError(
|
||||
f"Deserialization of {msg_type} is not supported.",
|
||||
)
|
||||
return _MSGS[msg_type](**js_msg)
|
||||
|
||||
|
||||
def serialize(messages: Union[Sequence[MessageBase], MessageBase]) -> str:
|
||||
"""Serialize multiple MessageBase instance"""
|
||||
if isinstance(messages, MessageBase):
|
||||
return messages.serialize()
|
||||
seq = [msg.serialize() for msg in messages]
|
||||
return json.dumps({"__type": "List", "__value": seq})
|
||||
189
AlgoriAgent/src/agentscope/models/__init__.py
Normal file
189
AlgoriAgent/src/agentscope/models/__init__.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Import modules in models package."""
|
||||
import json
|
||||
from typing import Union, Type
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from .config import _ModelConfig
|
||||
from .model import ModelWrapperBase
|
||||
from .response import ModelResponse
|
||||
from .post_model import (
|
||||
PostAPIModelWrapperBase,
|
||||
PostAPIChatWrapper,
|
||||
)
|
||||
from .openai_model import (
|
||||
OpenAIWrapperBase,
|
||||
OpenAIChatWrapper,
|
||||
OpenAIDALLEWrapper,
|
||||
OpenAIEmbeddingWrapper,
|
||||
)
|
||||
from .dashscope_model import (
|
||||
DashScopeChatWrapper,
|
||||
DashScopeImageSynthesisWrapper,
|
||||
DashScopeTextEmbeddingWrapper,
|
||||
DashScopeMultiModalWrapper,
|
||||
)
|
||||
from .ollama_model import (
|
||||
OllamaChatWrapper,
|
||||
OllamaEmbeddingWrapper,
|
||||
OllamaGenerationWrapper,
|
||||
)
|
||||
from .gemini_model import (
|
||||
GeminiChatWrapper,
|
||||
GeminiEmbeddingWrapper,
|
||||
)
|
||||
from .zhipu_model import (
|
||||
ZhipuAIChatWrapper,
|
||||
ZhipuAIEmbeddingWrapper,
|
||||
)
|
||||
from .litellm_model import (
|
||||
LiteLLMChatWrapper,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ModelWrapperBase",
|
||||
"ModelResponse",
|
||||
"PostAPIModelWrapperBase",
|
||||
"PostAPIChatWrapper",
|
||||
"OpenAIWrapperBase",
|
||||
"OpenAIChatWrapper",
|
||||
"OpenAIDALLEWrapper",
|
||||
"OpenAIEmbeddingWrapper",
|
||||
"DashScopeChatWrapper",
|
||||
"DashScopeImageSynthesisWrapper",
|
||||
"DashScopeTextEmbeddingWrapper",
|
||||
"DashScopeMultiModalWrapper",
|
||||
"OllamaChatWrapper",
|
||||
"OllamaEmbeddingWrapper",
|
||||
"OllamaGenerationWrapper",
|
||||
"GeminiChatWrapper",
|
||||
"GeminiEmbeddingWrapper",
|
||||
"ZhipuAIChatWrapper",
|
||||
"ZhipuAIEmbeddingWrapper",
|
||||
"LiteLLMChatWrapper",
|
||||
"load_model_by_config_name",
|
||||
"load_config_by_name",
|
||||
"read_model_configs",
|
||||
"clear_model_configs",
|
||||
]
|
||||
|
||||
_MODEL_CONFIGS: dict[str, dict] = {}
|
||||
|
||||
|
||||
def _get_model_wrapper(model_type: str) -> Type[ModelWrapperBase]:
|
||||
"""Get the specific type of model wrapper
|
||||
|
||||
Args:
|
||||
model_type (`str`): The model type name.
|
||||
|
||||
Returns:
|
||||
`Type[ModelWrapperBase]`: The corresponding model wrapper class.
|
||||
"""
|
||||
wrapper = ModelWrapperBase.get_wrapper(model_type=model_type)
|
||||
if wrapper is None:
|
||||
logger.warning(
|
||||
f"Unsupported model_type [{model_type}],"
|
||||
"use PostApiModelWrapper instead.",
|
||||
)
|
||||
return PostAPIModelWrapperBase
|
||||
return wrapper
|
||||
|
||||
|
||||
def load_config_by_name(config_name: str) -> Union[dict, None]:
|
||||
"""Load the model config by name, and return the config dict."""
|
||||
return _MODEL_CONFIGS.get(config_name, None)
|
||||
|
||||
|
||||
def load_model_by_config_name(config_name: str) -> ModelWrapperBase:
|
||||
"""Load the model by config name, and return the model wrapper."""
|
||||
if len(_MODEL_CONFIGS) == 0:
|
||||
raise ValueError(
|
||||
"No model configs loaded, please call "
|
||||
"`read_model_configs` first.",
|
||||
)
|
||||
|
||||
# Find model config by name
|
||||
if config_name not in _MODEL_CONFIGS:
|
||||
raise ValueError(
|
||||
f"Cannot find [{config_name}] in loaded configurations.",
|
||||
)
|
||||
config = _MODEL_CONFIGS.get(config_name, None)
|
||||
|
||||
if config is None:
|
||||
raise ValueError(
|
||||
f"Cannot find [{config_name}] in loaded configurations.",
|
||||
)
|
||||
|
||||
model_type = config.model_type
|
||||
|
||||
kwargs = {k: v for k, v in config.items() if k != "model_type"}
|
||||
|
||||
return _get_model_wrapper(model_type=model_type)(**kwargs)
|
||||
|
||||
|
||||
def clear_model_configs() -> None:
|
||||
"""Clear the loaded model configs."""
|
||||
_MODEL_CONFIGS.clear()
|
||||
|
||||
|
||||
def read_model_configs(
|
||||
configs: Union[dict, str, list],
|
||||
clear_existing: bool = False,
|
||||
) -> None:
|
||||
"""read model configs from a path or a list of dicts.
|
||||
|
||||
Args:
|
||||
configs (`Union[str, list, dict]`):
|
||||
The path of the model configs | a config dict | a list of model
|
||||
configs.
|
||||
clear_existing (`bool`, defaults to `False`):
|
||||
Whether to clear the loaded model configs before reading.
|
||||
|
||||
Returns:
|
||||
`dict`:
|
||||
The model configs.
|
||||
"""
|
||||
if clear_existing:
|
||||
clear_model_configs()
|
||||
|
||||
cfgs = None
|
||||
|
||||
if isinstance(configs, str):
|
||||
with open(configs, "r", encoding="utf-8") as f:
|
||||
cfgs = json.load(f)
|
||||
|
||||
if isinstance(configs, dict):
|
||||
cfgs = [configs]
|
||||
|
||||
if isinstance(configs, list):
|
||||
if not all(isinstance(_, dict) for _ in configs):
|
||||
raise ValueError(
|
||||
"The model config unit should be a dict.",
|
||||
)
|
||||
cfgs = configs
|
||||
|
||||
if cfgs is None:
|
||||
raise TypeError(
|
||||
f"Invalid type of model_configs, it could be a dict, a list of "
|
||||
f"dicts, or a path to a json file (containing a dict or a list "
|
||||
f"of dicts), but got {type(configs)}",
|
||||
)
|
||||
|
||||
format_configs = _ModelConfig.format_configs(configs=cfgs)
|
||||
|
||||
# check if name is unique
|
||||
for cfg in format_configs:
|
||||
if cfg.config_name in _MODEL_CONFIGS:
|
||||
logger.warning(
|
||||
f"config_name [{cfg.config_name}] already exists.",
|
||||
)
|
||||
continue
|
||||
_MODEL_CONFIGS[cfg.config_name] = cfg
|
||||
|
||||
# print the loaded model configs
|
||||
logger.info(
|
||||
"Load configs for model wrapper: {}",
|
||||
", ".join(_MODEL_CONFIGS.keys()),
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
59
AlgoriAgent/src/agentscope/models/config.py
Normal file
59
AlgoriAgent/src/agentscope/models/config.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""The model config."""
|
||||
from typing import Union, Sequence, Any
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class _ModelConfig(dict):
|
||||
"""Base class for model config."""
|
||||
|
||||
__getattr__ = dict.__getitem__
|
||||
__setattr__ = dict.__setitem__
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config_name: str,
|
||||
model_type: str = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Initialize the config with the given arguments, and checking the
|
||||
type of the arguments.
|
||||
|
||||
Args:
|
||||
config_name (`str`): A unique name of the model config.
|
||||
model_type (`str`, optional): The class name (or its model type) of
|
||||
the generated model wrapper. Defaults to None.
|
||||
|
||||
Raises:
|
||||
`ValueError`: If `config_name` is not provided.
|
||||
"""
|
||||
if config_name is None:
|
||||
raise ValueError("The `config_name` field is required for Cfg")
|
||||
if model_type is None:
|
||||
logger.warning(
|
||||
f"`model_type` is not provided in config [{config_name}],"
|
||||
" use `PostAPIModelWrapperBase` by default.",
|
||||
)
|
||||
super().__init__(
|
||||
config_name=config_name,
|
||||
model_type=model_type,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def format_configs(
|
||||
cls,
|
||||
configs: Union[Sequence[dict], dict],
|
||||
) -> Sequence:
|
||||
"""Covert config dicts into a list of _ModelConfig.
|
||||
|
||||
Args:
|
||||
configs (Union[Sequence[dict], dict]): configs in dict format.
|
||||
|
||||
Returns:
|
||||
Sequence[_ModelConfig]: converted ModelConfig list.
|
||||
"""
|
||||
if isinstance(configs, dict):
|
||||
return [_ModelConfig(**configs)]
|
||||
return [_ModelConfig(**cfg) for cfg in configs]
|
||||
847
AlgoriAgent/src/agentscope/models/dashscope_model.py
Normal file
847
AlgoriAgent/src/agentscope/models/dashscope_model.py
Normal file
@@ -0,0 +1,847 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Model wrapper for DashScope models"""
|
||||
import os
|
||||
from abc import ABC
|
||||
from http import HTTPStatus
|
||||
from typing import Any, Union, List, Sequence
|
||||
from loguru import logger
|
||||
|
||||
from ..message import Msg
|
||||
from ..utils.tools import _convert_to_str, _guess_type_by_extension
|
||||
|
||||
try:
|
||||
import dashscope
|
||||
except ImportError:
|
||||
dashscope = None
|
||||
|
||||
from .model import ModelWrapperBase, ModelResponse
|
||||
|
||||
from ..file_manager import file_manager
|
||||
|
||||
|
||||
class DashScopeWrapperBase(ModelWrapperBase, ABC):
|
||||
"""The model wrapper for DashScope API."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config_name: str,
|
||||
model_name: str = None,
|
||||
api_key: str = None,
|
||||
generate_args: dict = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the DashScope wrapper.
|
||||
|
||||
Args:
|
||||
config_name (`str`):
|
||||
The name of the model config.
|
||||
model_name (`str`, default `None`):
|
||||
The name of the model to use in DashScope API.
|
||||
api_key (`str`, default `None`):
|
||||
The API key for DashScope API.
|
||||
generate_args (`dict`, default `None`):
|
||||
The extra keyword arguments used in DashScope api generation,
|
||||
e.g. `temperature`, `seed`.
|
||||
"""
|
||||
if model_name is None:
|
||||
model_name = config_name
|
||||
logger.warning("model_name is not set, use config_name instead.")
|
||||
|
||||
super().__init__(config_name=config_name)
|
||||
|
||||
if dashscope is None:
|
||||
raise ImportError(
|
||||
"Cannot find dashscope package in current python environment.",
|
||||
)
|
||||
|
||||
self.model_name = model_name
|
||||
self.generate_args = generate_args or {}
|
||||
|
||||
self.api_key = api_key
|
||||
dashscope.api_key = self.api_key
|
||||
self.max_length = None
|
||||
|
||||
# Set monitor accordingly
|
||||
self._register_default_metrics()
|
||||
|
||||
def format(
|
||||
self,
|
||||
*args: Union[Msg, Sequence[Msg]],
|
||||
) -> Union[List[dict], str]:
|
||||
raise RuntimeError(
|
||||
f"Model Wrapper [{type(self).__name__}] doesn't "
|
||||
f"need to format the input. Please try to use the "
|
||||
f"model wrapper directly.",
|
||||
)
|
||||
|
||||
|
||||
class DashScopeChatWrapper(DashScopeWrapperBase):
|
||||
"""The model wrapper for DashScope's chat API, refer to
|
||||
https://help.aliyun.com/zh/dashscope/developer-reference/api-details
|
||||
"""
|
||||
|
||||
model_type: str = "dashscope_chat"
|
||||
|
||||
deprecated_model_type: str = "tongyi_chat"
|
||||
|
||||
def _register_default_metrics(self) -> None:
|
||||
# Set monitor accordingly
|
||||
# TODO: set quota to the following metrics
|
||||
self.monitor.register(
|
||||
self._metric("call_counter"),
|
||||
metric_unit="times",
|
||||
)
|
||||
self.monitor.register(
|
||||
self._metric("prompt_tokens"),
|
||||
metric_unit="token",
|
||||
)
|
||||
self.monitor.register(
|
||||
self._metric("completion_tokens"),
|
||||
metric_unit="token",
|
||||
)
|
||||
self.monitor.register(
|
||||
self._metric("total_tokens"),
|
||||
metric_unit="token",
|
||||
)
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
messages: list,
|
||||
**kwargs: Any,
|
||||
) -> ModelResponse:
|
||||
"""Processes a list of messages to construct a payload for the
|
||||
DashScope API call. It then makes a request to the DashScope API
|
||||
and returns the response. This method also updates monitoring
|
||||
metrics based on the API response.
|
||||
|
||||
Each message in the 'messages' list can contain text content and
|
||||
optionally an 'image_urls' key. If 'image_urls' is provided,
|
||||
it is expected to be a list of strings representing URLs to images.
|
||||
These URLs will be transformed to a suitable format for the DashScope
|
||||
API, which might involve converting local file paths to data URIs.
|
||||
|
||||
Args:
|
||||
messages (`list`):
|
||||
A list of messages to process.
|
||||
**kwargs (`Any`):
|
||||
The keyword arguments to DashScope chat completions API,
|
||||
e.g. `temperature`, `max_tokens`, `top_p`, etc. Please
|
||||
refer to
|
||||
https://help.aliyun.com/zh/dashscope/developer-reference/api-details
|
||||
for more detailed arguments.
|
||||
|
||||
Returns:
|
||||
`ModelResponse`:
|
||||
The response text in text field, and the raw response in
|
||||
raw field.
|
||||
|
||||
Note:
|
||||
`parse_func`, `fault_handler` and `max_retries` are reserved for
|
||||
`_response_parse_decorator` to parse and check the response
|
||||
generated by model wrapper. Their usages are listed as follows:
|
||||
- `parse_func` is a callable function used to parse and check
|
||||
the response generated by the model, which takes the response
|
||||
as input.
|
||||
- `max_retries` is the maximum number of retries when the
|
||||
`parse_func` raise an exception.
|
||||
- `fault_handler` is a callable function which is called
|
||||
when the response generated by the model is invalid after
|
||||
`max_retries` retries.
|
||||
The rule of roles in messages for DashScope is very rigid,
|
||||
for more details, please refer to
|
||||
https://help.aliyun.com/zh/dashscope/developer-reference/api-details
|
||||
"""
|
||||
|
||||
# step1: prepare keyword arguments
|
||||
kwargs = {**self.generate_args, **kwargs}
|
||||
|
||||
# step2: checking messages
|
||||
if not isinstance(messages, list):
|
||||
raise ValueError(
|
||||
"Dashscope `messages` field expected type `list`, "
|
||||
f"got `{type(messages)}` instead.",
|
||||
)
|
||||
if not all("role" in msg and "content" in msg for msg in messages):
|
||||
raise ValueError(
|
||||
"Each message in the 'messages' list must contain a 'role' "
|
||||
"and 'content' key for DashScope API.",
|
||||
)
|
||||
|
||||
# step3: forward to generate response
|
||||
response = dashscope.Generation.call(
|
||||
model=self.model_name,
|
||||
messages=messages,
|
||||
result_format="message", # set the result to be "message" format.
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
error_msg = (
|
||||
f" Request id: {response.request_id},"
|
||||
f" Status code: {response.status_code},"
|
||||
f" error code: {response.code},"
|
||||
f" error message: {response.message}."
|
||||
)
|
||||
|
||||
raise RuntimeError(error_msg)
|
||||
|
||||
# step4: record the api invocation if needed
|
||||
self._save_model_invocation(
|
||||
arguments={
|
||||
"model": self.model_name,
|
||||
"messages": messages,
|
||||
**kwargs,
|
||||
},
|
||||
response=response,
|
||||
)
|
||||
|
||||
# step5: update monitor accordingly
|
||||
# The metric names are unified for comparison
|
||||
self.update_monitor(
|
||||
call_counter=1,
|
||||
prompt_tokens=response.usage.get("input_tokens", 0),
|
||||
completion_tokens=response.usage.get("output_tokens", 0),
|
||||
total_tokens=response.usage.get("input_tokens", 0)
|
||||
+ response.usage.get("output_tokens", 0),
|
||||
)
|
||||
|
||||
# step6: return response
|
||||
return ModelResponse(
|
||||
text=response.output["choices"][0]["message"]["content"],
|
||||
raw=response,
|
||||
)
|
||||
|
||||
def format(
|
||||
self,
|
||||
*args: Union[Msg, Sequence[Msg]],
|
||||
) -> List:
|
||||
"""Format the messages for DashScope Chat API.
|
||||
|
||||
In this format function, the input messages are formatted into a
|
||||
single system messages with format "{name}: {content}" for each
|
||||
message. Note this strategy maybe not suitable for all scenarios,
|
||||
and developers are encouraged to implement their own prompt
|
||||
engineering strategies.
|
||||
|
||||
The following is an example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
prompt = model.format(
|
||||
Msg("system", "You're a helpful assistant", role="system"),
|
||||
Msg("Bob", "Hi, how can I help you?", role="assistant"),
|
||||
Msg("user", "What's the date today?", role="user")
|
||||
)
|
||||
|
||||
The prompt will be as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You're a helpful assistant",
|
||||
}
|
||||
{
|
||||
"role": "user",
|
||||
"content": (
|
||||
"## Dialogue History\\n"
|
||||
"Bob: Hi, how can I help you?\\n"
|
||||
"user: What's the date today?"
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Args:
|
||||
args (`Union[Msg, Sequence[Msg]]`):
|
||||
The input arguments to be formatted, where each argument
|
||||
should be a `Msg` object, or a list of `Msg` objects.
|
||||
In distribution, placeholder is also allowed.
|
||||
|
||||
Returns:
|
||||
`List[dict]`:
|
||||
The formatted messages.
|
||||
"""
|
||||
|
||||
# Parse all information into a list of messages
|
||||
input_msgs = []
|
||||
for _ in args:
|
||||
if _ is None:
|
||||
continue
|
||||
if isinstance(_, Msg):
|
||||
input_msgs.append(_)
|
||||
elif isinstance(_, list) and all(isinstance(__, Msg) for __ in _):
|
||||
input_msgs.extend(_)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"The input should be a Msg object or a list "
|
||||
f"of Msg objects, got {type(_)}.",
|
||||
)
|
||||
|
||||
messages = []
|
||||
|
||||
# record dialog history as a list of strings
|
||||
dialogue = []
|
||||
for i, unit in enumerate(input_msgs):
|
||||
if i == 0 and unit.role == "system":
|
||||
# system prompt
|
||||
messages.append(
|
||||
{
|
||||
"role": unit.role,
|
||||
"content": _convert_to_str(unit.content),
|
||||
},
|
||||
)
|
||||
else:
|
||||
# Merge all messages into a dialogue history prompt
|
||||
dialogue.append(
|
||||
f"{unit.name}: {_convert_to_str(unit.content)}",
|
||||
)
|
||||
|
||||
dialogue_history = "\n".join(dialogue)
|
||||
|
||||
user_content_template = "## Dialogue History\n{dialogue_history}"
|
||||
|
||||
messages.append(
|
||||
{
|
||||
"role": "user",
|
||||
"content": user_content_template.format(
|
||||
dialogue_history=dialogue_history,
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
class DashScopeImageSynthesisWrapper(DashScopeWrapperBase):
|
||||
"""The model wrapper for DashScope Image Synthesis API, refer to
|
||||
https://help.aliyun.com/zh/dashscope/developer-reference/quick-start-1
|
||||
"""
|
||||
|
||||
model_type: str = "dashscope_image_synthesis"
|
||||
|
||||
def _register_default_metrics(self) -> None:
|
||||
# Set monitor accordingly
|
||||
# TODO: set quota to the following metrics
|
||||
self.monitor.register(
|
||||
self._metric("call_counter"),
|
||||
metric_unit="times",
|
||||
)
|
||||
self.monitor.register(
|
||||
self._metric("image_count"),
|
||||
metric_unit="image",
|
||||
)
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
prompt: str,
|
||||
save_local: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> ModelResponse:
|
||||
"""
|
||||
Args:
|
||||
prompt (`str`):
|
||||
The prompt string to generate images from.
|
||||
save_local: (`bool`, default `False`):
|
||||
Whether to save the generated images locally, and replace
|
||||
the returned image url with the local path.
|
||||
**kwargs (`Any`):
|
||||
The keyword arguments to DashScope Image Synthesis API,
|
||||
e.g. `n`, `size`, etc. Please refer to
|
||||
https://help.aliyun.com/zh/dashscope/developer-reference/api-details-9
|
||||
for more detailed arguments.
|
||||
|
||||
Returns:
|
||||
`ModelResponse`:
|
||||
A list of image urls in image_urls field and the
|
||||
raw response in raw field.
|
||||
|
||||
Note:
|
||||
`parse_func`, `fault_handler` and `max_retries` are reserved
|
||||
for `_response_parse_decorator` to parse and check the
|
||||
response generated by model wrapper. Their usages are listed
|
||||
as follows:
|
||||
- `parse_func` is a callable function used to parse and
|
||||
check the response generated by the model, which takes
|
||||
the response as input.
|
||||
- `max_retries` is the maximum number of retries when the
|
||||
`parse_func` raise an exception.
|
||||
- `fault_handler` is a callable function which is called
|
||||
when the response generated by the model is invalid after
|
||||
`max_retries` retries.
|
||||
"""
|
||||
# step1: prepare keyword arguments
|
||||
kwargs = {**self.generate_args, **kwargs}
|
||||
|
||||
# step2: forward to generate response
|
||||
response = dashscope.ImageSynthesis.call(
|
||||
model=self.model_name,
|
||||
prompt=prompt,
|
||||
**kwargs,
|
||||
)
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
error_msg = (
|
||||
f" Request id: {response.request_id},"
|
||||
f" Status code: {response.status_code},"
|
||||
f" error code: {response.code},"
|
||||
f" error message: {response.message}."
|
||||
)
|
||||
raise RuntimeError(error_msg)
|
||||
|
||||
# step3: record the model api invocation if needed
|
||||
self._save_model_invocation(
|
||||
arguments={
|
||||
"model": self.model_name,
|
||||
"prompt": prompt,
|
||||
**kwargs,
|
||||
},
|
||||
response=response,
|
||||
)
|
||||
|
||||
# step4: update monitor accordingly
|
||||
self.update_monitor(
|
||||
call_counter=1,
|
||||
**response.usage,
|
||||
)
|
||||
|
||||
# step5: return response
|
||||
images = response.output["results"]
|
||||
# Get image urls as a list
|
||||
urls = [_["url"] for _ in images]
|
||||
|
||||
if save_local:
|
||||
# Return local url if save_local is True
|
||||
urls = [file_manager.save_image(_) for _ in urls]
|
||||
return ModelResponse(image_urls=urls, raw=response)
|
||||
|
||||
|
||||
class DashScopeTextEmbeddingWrapper(DashScopeWrapperBase):
|
||||
"""The model wrapper for DashScope Text Embedding API."""
|
||||
|
||||
model_type: str = "dashscope_text_embedding"
|
||||
|
||||
def _register_default_metrics(self) -> None:
|
||||
# Set monitor accordingly
|
||||
# TODO: set quota to the following metrics
|
||||
self.monitor.register(
|
||||
self._metric("call_counter"),
|
||||
metric_unit="times",
|
||||
)
|
||||
self.monitor.register(
|
||||
self._metric("total_tokens"),
|
||||
metric_unit="token",
|
||||
)
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
texts: Union[list[str], str],
|
||||
**kwargs: Any,
|
||||
) -> ModelResponse:
|
||||
"""Embed the messages with DashScope Text Embedding API.
|
||||
|
||||
Args:
|
||||
texts (`list[str]` or `str`):
|
||||
The messages used to embed.
|
||||
**kwargs (`Any`):
|
||||
The keyword arguments to DashScope Text Embedding API,
|
||||
e.g. `text_type`. Please refer to
|
||||
https://help.aliyun.com/zh/dashscope/developer-reference/api-details-15
|
||||
for more detailed arguments.
|
||||
|
||||
Returns:
|
||||
`ModelResponse`:
|
||||
A list of embeddings in embedding field and the raw
|
||||
response in raw field.
|
||||
|
||||
Note:
|
||||
`parse_func`, `fault_handler` and `max_retries` are reserved
|
||||
for `_response_parse_decorator` to parse and check the response
|
||||
generated by model wrapper. Their usages are listed as follows:
|
||||
- `parse_func` is a callable function used to parse and
|
||||
check the response generated by the model, which takes the
|
||||
response as input.
|
||||
- `max_retries` is the maximum number of retries when the
|
||||
`parse_func` raise an exception.
|
||||
- `fault_handler` is a callable function which is called
|
||||
when the response generated by the model is invalid after
|
||||
`max_retries` retries.
|
||||
"""
|
||||
# step1: prepare keyword arguments
|
||||
kwargs = {**self.generate_args, **kwargs}
|
||||
|
||||
# step2: forward to generate response
|
||||
response = dashscope.TextEmbedding.call(
|
||||
input=texts,
|
||||
model=self.model_name,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
error_msg = (
|
||||
f" Request id: {response.request_id},"
|
||||
f" Status code: {response.status_code},"
|
||||
f" error code: {response.code},"
|
||||
f" error message: {response.message}."
|
||||
)
|
||||
raise RuntimeError(error_msg)
|
||||
|
||||
# step3: record the model api invocation if needed
|
||||
self._save_model_invocation(
|
||||
arguments={
|
||||
"model": self.model_name,
|
||||
"input": texts,
|
||||
**kwargs,
|
||||
},
|
||||
response=response,
|
||||
)
|
||||
|
||||
# step4: update monitor accordingly
|
||||
self.update_monitor(
|
||||
call_counter=1,
|
||||
**response.usage,
|
||||
)
|
||||
|
||||
# step5: return response
|
||||
return ModelResponse(
|
||||
embedding=[_["embedding"] for _ in response.output["embeddings"]],
|
||||
raw=response,
|
||||
)
|
||||
|
||||
|
||||
class DashScopeMultiModalWrapper(DashScopeWrapperBase):
|
||||
"""The model wrapper for DashScope Multimodal API, refer to
|
||||
https://help.aliyun.com/zh/dashscope/developer-reference/tongyi-qianwen-vl-api
|
||||
"""
|
||||
|
||||
model_type: str = "dashscope_multimodal"
|
||||
|
||||
def _register_default_metrics(self) -> None:
|
||||
# Set monitor accordingly
|
||||
# TODO: set quota to the following metrics
|
||||
self.monitor.register(
|
||||
self._metric("call_counter"),
|
||||
metric_unit="times",
|
||||
)
|
||||
self.monitor.register(
|
||||
self._metric("prompt_tokens"),
|
||||
metric_unit="token",
|
||||
)
|
||||
self.monitor.register(
|
||||
self._metric("completion_tokens"),
|
||||
metric_unit="token",
|
||||
)
|
||||
self.monitor.register(
|
||||
self._metric("total_tokens"),
|
||||
metric_unit="token",
|
||||
)
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
messages: list,
|
||||
**kwargs: Any,
|
||||
) -> ModelResponse:
|
||||
"""Model call for DashScope MultiModal API.
|
||||
|
||||
Args:
|
||||
messages (`list`):
|
||||
A list of messages to process.
|
||||
**kwargs (`Any`):
|
||||
The keyword arguments to DashScope MultiModal API,
|
||||
e.g. `stream`. Please refer to
|
||||
https://help.aliyun.com/zh/dashscope/developer-reference/tongyi-qianwen-vl-plus-api
|
||||
for more detailed arguments.
|
||||
|
||||
Returns:
|
||||
`ModelResponse`:
|
||||
The response text in text field, and the raw response in
|
||||
raw field.
|
||||
|
||||
Note:
|
||||
If involving image links, then the messages should be of the
|
||||
following form:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": [
|
||||
{"text": "You are a helpful assistant."},
|
||||
],
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"text": "What does this picture depict?"},
|
||||
{"image": "http://example.com/image.jpg"},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
Therefore, you should input a list matching the content value
|
||||
above.
|
||||
If only involving words, just input them.
|
||||
|
||||
`parse_func`, `fault_handler` and `max_retries` are reserved
|
||||
for `_response_parse_decorator` to parse and check the response
|
||||
generated by model wrapper. Their usages are listed as follows:
|
||||
- `parse_func` is a callable function used to parse and
|
||||
check the response generated by the model, which takes the
|
||||
response as input.
|
||||
- `max_retries` is the maximum number of retries when the
|
||||
`parse_func` raise an exception.
|
||||
- `fault_handler` is a callable function which is called
|
||||
when the response generated by the model is invalid after
|
||||
`max_retries` retries.
|
||||
"""
|
||||
# step1: prepare keyword arguments
|
||||
kwargs = {**self.generate_args, **kwargs}
|
||||
|
||||
# step2: forward to generate response
|
||||
response = dashscope.MultiModalConversation.call(
|
||||
model=self.model_name,
|
||||
messages=messages,
|
||||
**kwargs,
|
||||
)
|
||||
# Unhandle code path here
|
||||
# response could be a generator , if stream is yes
|
||||
# suggest add a check here
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
error_msg = (
|
||||
f" Request id: {response.request_id},"
|
||||
f" Status code: {response.status_code},"
|
||||
f" error code: {response.code},"
|
||||
f" error message: {response.message}."
|
||||
)
|
||||
raise RuntimeError(error_msg)
|
||||
|
||||
# step3: record the model api invocation if needed
|
||||
self._save_model_invocation(
|
||||
arguments={
|
||||
"model": self.model_name,
|
||||
"messages": messages,
|
||||
**kwargs,
|
||||
},
|
||||
response=response,
|
||||
)
|
||||
|
||||
# step4: update monitor accordingly
|
||||
input_tokens = response.usage.get("input_tokens", 0)
|
||||
image_tokens = response.usage.get("image_tokens", 0)
|
||||
audio_tokens = response.usage.get("audio_tokens", 0)
|
||||
output_tokens = response.usage.get("output_tokens", 0)
|
||||
self.update_monitor(
|
||||
call_counter=1,
|
||||
prompt_tokens=input_tokens,
|
||||
completion_tokens=output_tokens,
|
||||
total_tokens=input_tokens
|
||||
+ output_tokens
|
||||
+ image_tokens
|
||||
+ audio_tokens,
|
||||
)
|
||||
|
||||
# step5: return response
|
||||
content = response.output["choices"][0]["message"]["content"]
|
||||
if isinstance(content, list):
|
||||
content = content[0]["text"]
|
||||
|
||||
return ModelResponse(
|
||||
text=content,
|
||||
raw=response,
|
||||
)
|
||||
|
||||
def format(
|
||||
self,
|
||||
*args: Union[Msg, Sequence[Msg]],
|
||||
) -> List:
|
||||
"""Format the messages for DashScope Multimodal API.
|
||||
|
||||
The multimodal API has the following requirements:
|
||||
|
||||
- The roles of messages must alternate between "user" and
|
||||
"assistant".
|
||||
- The message with the role "system" should be the first message
|
||||
in the list.
|
||||
- If the system message exists, then the second message must
|
||||
have the role "user".
|
||||
- The last message in the list should have the role "user".
|
||||
- In each message, more than one figure is allowed.
|
||||
|
||||
With the above requirements, we format the messages as follows:
|
||||
|
||||
- If the first message is a system message, then we will keep it as
|
||||
system prompt.
|
||||
- We merge all messages into a dialogue history prompt in a single
|
||||
message with the role "user".
|
||||
- When there are multiple figures in the given messages, we will
|
||||
attach it to the user message by order. Note if there are
|
||||
multiple figures, this strategy may cause misunderstanding for
|
||||
the model. For advanced solutions, developers are encouraged to
|
||||
implement their own prompt engineering strategies.
|
||||
|
||||
The following is an example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
prompt = model.format(
|
||||
Msg(
|
||||
"system",
|
||||
"You're a helpful assistant",
|
||||
role="system", url="figure1"
|
||||
),
|
||||
Msg(
|
||||
"Bob",
|
||||
"How about this picture?",
|
||||
role="assistant", url="figure2"
|
||||
),
|
||||
Msg(
|
||||
"user",
|
||||
"It's wonderful! How about mine?",
|
||||
role="user", image="figure3"
|
||||
)
|
||||
)
|
||||
|
||||
The prompt will be as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
{
|
||||
"role": "system",
|
||||
"content": [
|
||||
{"text": "You are a helpful assistant"},
|
||||
{"image": "figure1"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"image": "figure2"},
|
||||
{"image": "figure3"},
|
||||
{
|
||||
"text": (
|
||||
"## Dialogue History\\n"
|
||||
"Bob: How about this picture?\\n"
|
||||
"user: It's wonderful! How about mine?"
|
||||
)
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
Note:
|
||||
In multimodal API, the url of local files should be prefixed with
|
||||
"file://", which will be attached in this format function.
|
||||
|
||||
Args:
|
||||
args (`Union[Msg, Sequence[Msg]]`):
|
||||
The input arguments to be formatted, where each argument
|
||||
should be a `Msg` object, or a list of `Msg` objects.
|
||||
In distribution, placeholder is also allowed.
|
||||
|
||||
Returns:
|
||||
`List[dict]`:
|
||||
The formatted messages.
|
||||
"""
|
||||
|
||||
# Parse all information into a list of messages
|
||||
input_msgs = []
|
||||
for _ in args:
|
||||
if _ is None:
|
||||
continue
|
||||
if isinstance(_, Msg):
|
||||
input_msgs.append(_)
|
||||
elif isinstance(_, list) and all(isinstance(__, Msg) for __ in _):
|
||||
input_msgs.extend(_)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"The input should be a Msg object or a list "
|
||||
f"of Msg objects, got {type(_)}.",
|
||||
)
|
||||
|
||||
messages = []
|
||||
|
||||
# record dialog history as a list of strings
|
||||
dialogue = []
|
||||
image_or_audio_dicts = []
|
||||
for i, unit in enumerate(input_msgs):
|
||||
if i == 0 and unit.role == "system":
|
||||
# system prompt
|
||||
content = self.convert_url(unit.url)
|
||||
content.append({"text": _convert_to_str(unit.content)})
|
||||
|
||||
messages.append(
|
||||
{
|
||||
"role": unit.role,
|
||||
"content": content,
|
||||
},
|
||||
)
|
||||
else:
|
||||
# text message
|
||||
dialogue.append(
|
||||
f"{unit.name}: {_convert_to_str(unit.content)}",
|
||||
)
|
||||
# image and audio
|
||||
image_or_audio_dicts.extend(self.convert_url(unit.url))
|
||||
|
||||
dialogue_history = "\n".join(dialogue)
|
||||
|
||||
user_content_template = "## Dialogue History\n{dialogue_history}"
|
||||
|
||||
messages.append(
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
# Place the image or audio before the dialogue history
|
||||
*image_or_audio_dicts,
|
||||
{
|
||||
"text": user_content_template.format(
|
||||
dialogue_history=dialogue_history,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
return messages
|
||||
|
||||
def convert_url(self, url: Union[str, Sequence[str], None]) -> List[dict]:
|
||||
"""Convert the url to the format of DashScope API. Note for local
|
||||
files, a prefix "file://" will be added.
|
||||
|
||||
Args:
|
||||
url (`Union[str, Sequence[str], None]`):
|
||||
A string of url of a list of urls to be converted.
|
||||
|
||||
Returns:
|
||||
`List[dict]`:
|
||||
A list of dictionaries with key as the type of the url
|
||||
and value as the url. Only "image" and "audio" are supported.
|
||||
"""
|
||||
if url is None:
|
||||
return []
|
||||
|
||||
if isinstance(url, str):
|
||||
url_type = _guess_type_by_extension(url)
|
||||
if url_type in ["audio", "image"]:
|
||||
# Add prefix for local files
|
||||
if os.path.exists(url):
|
||||
url = "file://" + url
|
||||
return [{url_type: url}]
|
||||
else:
|
||||
# skip unsupported url
|
||||
logger.warning(
|
||||
f"Skip unsupported url ({url_type}), "
|
||||
f"expect image or audio.",
|
||||
)
|
||||
return []
|
||||
elif isinstance(url, list):
|
||||
dicts = []
|
||||
for _ in url:
|
||||
dicts.extend(self.convert_url(_))
|
||||
return dicts
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Unsupported url type {type(url)}, " f"str or list expected.",
|
||||
)
|
||||
417
AlgoriAgent/src/agentscope/models/gemini_model.py
Normal file
417
AlgoriAgent/src/agentscope/models/gemini_model.py
Normal file
@@ -0,0 +1,417 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Google Gemini model wrapper."""
|
||||
import os
|
||||
from abc import ABC
|
||||
from collections.abc import Iterable
|
||||
from typing import Sequence, Union, Any, List
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from agentscope.message import Msg
|
||||
from agentscope.models import ModelWrapperBase, ModelResponse
|
||||
from agentscope.utils.tools import _convert_to_str
|
||||
|
||||
try:
|
||||
import google.generativeai as genai
|
||||
|
||||
# This package will be installed when the google-generativeai is installed
|
||||
import google.ai.generativelanguage as glm
|
||||
except ImportError:
|
||||
genai = None
|
||||
glm = None
|
||||
|
||||
|
||||
class GeminiWrapperBase(ModelWrapperBase, ABC):
|
||||
"""The base class for Google Gemini model wrapper."""
|
||||
|
||||
_generation_method = None
|
||||
"""The generation method used in `__call__` function, which is used to
|
||||
filter models in `list_models` function."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config_name: str,
|
||||
model_name: str,
|
||||
api_key: str = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the wrapper for Google Gemini model.
|
||||
|
||||
Args:
|
||||
model_name (`str`):
|
||||
The name of the model.
|
||||
api_key (`str`, defaults to `None`):
|
||||
The api_key for the model. If it is not provided, it will be
|
||||
loaded from environment variable.
|
||||
"""
|
||||
super().__init__(config_name=config_name)
|
||||
|
||||
# Test if the required package is installed
|
||||
if genai is None:
|
||||
raise ImportError(
|
||||
"The google-generativeai package is not installed, "
|
||||
"please install it first.",
|
||||
)
|
||||
|
||||
# Load the api_key from argument or environment variable
|
||||
api_key = api_key or os.environ.get("GOOGLE_API_KEY")
|
||||
|
||||
if api_key is None:
|
||||
raise ValueError(
|
||||
"Google api_key must be provided or set as an "
|
||||
"environment variable.",
|
||||
)
|
||||
|
||||
genai.configure(api_key=api_key, **kwargs)
|
||||
|
||||
self.model_name = model_name
|
||||
|
||||
self._register_default_metrics()
|
||||
|
||||
def _register_default_metrics(self) -> None:
|
||||
"""Register the default metrics for the model."""
|
||||
raise NotImplementedError(
|
||||
"The method `_register_default_metrics` must be implemented.",
|
||||
)
|
||||
|
||||
def list_models(self) -> Sequence:
|
||||
"""List all available models for this API calling."""
|
||||
support_models = list(genai.list_models())
|
||||
|
||||
if self.generation_method is None:
|
||||
return support_models
|
||||
else:
|
||||
return [
|
||||
_
|
||||
for _ in support_models
|
||||
if self._generation_method in _.supported_generation_methods
|
||||
]
|
||||
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> ModelResponse:
|
||||
"""Processing input with the model."""
|
||||
raise NotImplementedError(
|
||||
f"Model Wrapper [{type(self).__name__}]"
|
||||
f" is missing the the required `__call__`"
|
||||
f" method.",
|
||||
)
|
||||
|
||||
|
||||
class GeminiChatWrapper(GeminiWrapperBase):
|
||||
"""The wrapper for Google Gemini chat model, e.g. gemini-pro"""
|
||||
|
||||
model_type: str = "gemini_chat"
|
||||
"""The type of the model, which is used in model configuration."""
|
||||
|
||||
generation_method = "generateContent"
|
||||
"""The generation method used in `__call__` function."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config_name: str,
|
||||
model_name: str,
|
||||
api_key: str = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
config_name=config_name,
|
||||
model_name=model_name,
|
||||
api_key=api_key,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# Create the generative model
|
||||
self.model = genai.GenerativeModel(model_name, **kwargs)
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
contents: Union[Sequence, str],
|
||||
stream: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> ModelResponse:
|
||||
"""Generate response for the given contents.
|
||||
|
||||
Args:
|
||||
contents (`Union[Sequence, str]`):
|
||||
The content to generate response.
|
||||
stream (`bool`, defaults to `False`):
|
||||
Whether to use stream mode.
|
||||
**kwargs:
|
||||
The additional arguments for generating response.
|
||||
|
||||
Returns:
|
||||
`ModelResponse`:
|
||||
The response text in text field, and the raw response in raw
|
||||
field.
|
||||
"""
|
||||
# step1: checking messages
|
||||
if isinstance(contents, Iterable):
|
||||
pass
|
||||
elif not isinstance(contents, str):
|
||||
logger.warning(
|
||||
"The input content is not a string or a list of "
|
||||
"messages, which may cause unexpected behavior.",
|
||||
)
|
||||
|
||||
# step2: forward to generate response
|
||||
# TODO: support response in stream mode
|
||||
response = self.model.generate_content(
|
||||
contents,
|
||||
stream=stream,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# step3: Check for candidates and handle accordingly
|
||||
if (
|
||||
not response.candidates[0].content
|
||||
or not response.candidates[0].content.parts
|
||||
or not response.candidates[0].content.parts[0].text
|
||||
):
|
||||
# If we cannot get the response text from the model
|
||||
finish_reason = response.candidates[0].finish_reason
|
||||
reasons = glm.Candidate.FinishReason
|
||||
|
||||
if finish_reason == reasons.STOP:
|
||||
error_info = (
|
||||
"Natural stop point of the model or provided stop "
|
||||
"sequence."
|
||||
)
|
||||
elif finish_reason == reasons.MAX_TOKENS:
|
||||
error_info = (
|
||||
"The maximum number of tokens as specified in the request "
|
||||
"was reached."
|
||||
)
|
||||
elif finish_reason == reasons.SAFETY:
|
||||
error_info = (
|
||||
"The candidate content was flagged for safety reasons."
|
||||
)
|
||||
elif finish_reason == reasons.RECITATION:
|
||||
error_info = (
|
||||
"The candidate content was flagged for recitation reasons."
|
||||
)
|
||||
elif finish_reason in [
|
||||
reasons.FINISH_REASON_UNSPECIFIED,
|
||||
reasons.OTHER,
|
||||
]:
|
||||
error_info = "Unknown error."
|
||||
else:
|
||||
error_info = "No information provided from Gemini API."
|
||||
|
||||
raise ValueError(
|
||||
"The Google Gemini API failed to generate text response with "
|
||||
f"the following finish reason: {error_info}\n"
|
||||
f"YOUR INPUT: {contents}\n"
|
||||
f"RAW RESPONSE FROM GEMINI API: {response}\n",
|
||||
)
|
||||
|
||||
# step4: record the api invocation if needed
|
||||
self._save_model_invocation(
|
||||
arguments={
|
||||
"contents": contents,
|
||||
"stream": stream,
|
||||
**kwargs,
|
||||
},
|
||||
response=str(response),
|
||||
)
|
||||
|
||||
# step5: update monitor accordingly
|
||||
token_prompt = self.model.count_tokens(contents).total_tokens
|
||||
token_response = self.model.count_tokens(response.text).total_tokens
|
||||
self.update_monitor(
|
||||
call_counter=1,
|
||||
completion_tokens=token_response,
|
||||
prompt_tokens=token_prompt,
|
||||
total_tokens=token_prompt + token_response,
|
||||
)
|
||||
|
||||
# step6: return response
|
||||
return ModelResponse(
|
||||
text=response.text,
|
||||
raw=response,
|
||||
)
|
||||
|
||||
def _register_default_metrics(self) -> None:
|
||||
"""Register the default metrics for the model."""
|
||||
self.monitor.register(
|
||||
self._metric("call_counter"),
|
||||
metric_unit="times",
|
||||
)
|
||||
self.monitor.register(
|
||||
self._metric("prompt_tokens"),
|
||||
metric_unit="token",
|
||||
)
|
||||
self.monitor.register(
|
||||
self._metric("completion_tokens"),
|
||||
metric_unit="token",
|
||||
)
|
||||
self.monitor.register(
|
||||
self._metric("total_tokens"),
|
||||
metric_unit="token",
|
||||
)
|
||||
|
||||
def format(
|
||||
self,
|
||||
*args: Union[Msg, Sequence[Msg]],
|
||||
) -> List[dict]:
|
||||
"""This function provide a basic prompting strategy for Gemini Chat
|
||||
API in multi-party conversation, which combines all input into a
|
||||
single string, and wrap it into a user message.
|
||||
|
||||
We make the above decision based on the following constraints of the
|
||||
Gemini generate API:
|
||||
|
||||
1. In Gemini `generate_content` API, the `role` field must be either
|
||||
`user` or `model`.
|
||||
|
||||
2. If we pass a list of messages to the `generate_content` API,
|
||||
the `user` role must speak in the beginning and end of the
|
||||
messages, and `user` and `model` must alternative. This prevents
|
||||
us to build a multi-party conversations, where `model` may keep
|
||||
speaking in different names.
|
||||
|
||||
The above information is updated to 2024/03/21. More information
|
||||
about the Gemini `generate_content` API can be found in
|
||||
https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/gemini
|
||||
|
||||
Based on the above considerations, we decide to combine all messages
|
||||
into a single user message. This is a simple and straightforward
|
||||
strategy, if you have any better ideas, pull request and
|
||||
discussion are welcome in our GitHub repository
|
||||
https://github.com/agentscope/agentscope!
|
||||
|
||||
Args:
|
||||
args (`Union[Msg, Sequence[Msg]]`):
|
||||
The input arguments to be formatted, where each argument
|
||||
should be a `Msg` object, or a list of `Msg` objects.
|
||||
In distribution, placeholder is also allowed.
|
||||
|
||||
Returns:
|
||||
`List[dict]`:
|
||||
A list with one user message.
|
||||
"""
|
||||
input_msgs = []
|
||||
for _ in args:
|
||||
if _ is None:
|
||||
continue
|
||||
if isinstance(_, Msg):
|
||||
input_msgs.append(_)
|
||||
elif isinstance(_, list) and all(isinstance(__, Msg) for __ in _):
|
||||
input_msgs.extend(_)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"The input should be a Msg object or a list "
|
||||
f"of Msg objects, got {type(_)}.",
|
||||
)
|
||||
|
||||
# record dialog history as a list of strings
|
||||
sys_prompt = None
|
||||
dialogue = []
|
||||
for i, unit in enumerate(input_msgs):
|
||||
if i == 0 and unit.role == "system":
|
||||
# system prompt
|
||||
sys_prompt = _convert_to_str(unit.content)
|
||||
else:
|
||||
# Merge all messages into a dialogue history prompt
|
||||
dialogue.append(
|
||||
f"{unit.name}: {_convert_to_str(unit.content)}",
|
||||
)
|
||||
|
||||
dialogue_history = "\n".join(dialogue)
|
||||
|
||||
if sys_prompt is None:
|
||||
user_content_template = "## Dialogue History\n{dialogue_history}"
|
||||
else:
|
||||
user_content_template = (
|
||||
"{sys_prompt}\n"
|
||||
"\n"
|
||||
"## Dialogue History\n"
|
||||
"{dialogue_history}"
|
||||
)
|
||||
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"parts": [
|
||||
user_content_template.format(
|
||||
sys_prompt=sys_prompt,
|
||||
dialogue_history=dialogue_history,
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
class GeminiEmbeddingWrapper(GeminiWrapperBase):
|
||||
"""The wrapper for Google Gemini embedding model,
|
||||
e.g. models/embedding-001"""
|
||||
|
||||
model_type: str = "gemini_embedding"
|
||||
"""The type of the model, which is used in model configuration."""
|
||||
|
||||
_generation_method = "embedContent"
|
||||
"""The generation method used in `__call__` function."""
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
content: Union[Sequence[Msg], str],
|
||||
task_type: str = None,
|
||||
title: str = None,
|
||||
**kwargs: Any,
|
||||
) -> ModelResponse:
|
||||
"""Generate embedding for the given content. More detailed information
|
||||
please refer to
|
||||
https://ai.google.dev/tutorials/python_quickstart#use_embeddings
|
||||
|
||||
Args:
|
||||
content (`Union[Sequence[Msg], str]`):
|
||||
The content to generate embedding.
|
||||
task_type (`str`, defaults to `None`):
|
||||
The type of the task.
|
||||
title (`str`, defaults to `None`):
|
||||
The title of the content.
|
||||
**kwargs:
|
||||
The additional arguments for generating embedding.
|
||||
|
||||
Returns:
|
||||
`ModelResponse`:
|
||||
The response embedding in embedding field, and the raw response
|
||||
in raw field.
|
||||
"""
|
||||
|
||||
# step1: forward to generate response
|
||||
response = genai.embed_content(
|
||||
model=self.model_name,
|
||||
content=content,
|
||||
task_type=task_type,
|
||||
title=title,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# step2: record the api invocation if needed
|
||||
self._save_model_invocation(
|
||||
arguments={
|
||||
"content": content,
|
||||
"task_type": task_type,
|
||||
"title": title,
|
||||
**kwargs,
|
||||
},
|
||||
response=response,
|
||||
)
|
||||
|
||||
# TODO: Up to 2023/03/11, the embedding model doesn't support to
|
||||
# count tokens.
|
||||
# step3: update monitor accordingly
|
||||
self.update_monitor(call_counter=1)
|
||||
|
||||
return ModelResponse(
|
||||
raw=response,
|
||||
embedding=response["embedding"],
|
||||
)
|
||||
|
||||
def _register_default_metrics(self) -> None:
|
||||
"""Register the default metrics for the model."""
|
||||
self.monitor.register(
|
||||
self._metric("call_counter"),
|
||||
metric_unit="times",
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user