Merge pull request #125 from fiatrete/MVP

MVP
This commit is contained in:
Liu Zhicong
2024-04-23 19:53:04 +08:00
committed by GitHub
188 changed files with 17018 additions and 3571 deletions
+3 -1
View File
@@ -9,5 +9,7 @@ history.txt
aios_shell_history.txt aios_shell_history.txt
math_school_env.db math_school_env.db
workflows.db workflows.db
venv-linux
venv_test
rootfs/test_doc/
+1
View File
@@ -0,0 +1 @@
disable=E0402
+10
View File
@@ -1,4 +1,14 @@
FROM python:3.11 FROM python:3.11
ENV PYTHON_VERSION=3.11
RUN apt-get update && apt-get install -y --no-install-recommends \
python${PYTHON_VERSION}-venv \
python${PYTHON_VERSION}-dev \
default-libmysqlclient-dev \
build-essential \
pkg-config \
&& rm -rf /var/lib/apt/lists/* \
WORKDIR /opt/aios WORKDIR /opt/aios
COPY ./src /opt/aios COPY ./src /opt/aios
COPY ./rootfs /opt/aios/app COPY ./rootfs /opt/aios/app
+160
View File
@@ -0,0 +1,160 @@
## LLM / AI 相关框架
### LLM Process
LLM调用封装的最小单元,提供了一系列最基础的支持
流程上 inpurt, prepare promot, llm_function_call_loop, post_llm , llmresult parser, AI Action
功能上 动态类型系统 load_from_config,llm_process_loader
### Agent
从Agent的视角定义了Agent的LLM行为逻辑
Process behavior (响应)
Task/Todo Loop (自主)
Self Loop (自省)
#### Agent.Memory
Memory模块设计的主要目的是能按一定的模式,在Token Limit的情况下构成Agent的一些上下文。
Memory的原始记录写入:原则上说,Agent的任何LLM行为都应至少将input/resp 写入Memory,毕竟LLM是非常高开销的行为。LLM的过程可以视情况写入(inner function的调用,action的执行,根据input构造的完整提示词等)
Memory的使用:LLM过程在构造提示词的“已知信息”部分时,通常都会有加载Memory的需求。尤其是在Input并不包含完整信息的情况下(非幂等LLM推理),更需要根据“上下文”来理解Input的含义。这在“ChatCompetition”过程中尤为明显。
使用Memory基本是两种方式
1. 加载写入的原始记录
2. 访问根据原始记录加工(Self-Thinking)后的Object-Summary。或则访问一个根据原始记录整理的,用文件系统方式组织的“记忆片段”
这里的核心痛点是:一个LLM过程,如何根据Input加载合适的Memory成为“已知信息”。
1. 如果明确的知道Input属于一个session,那么可以加载这个session相关的所有record(注意token limit和最大条数)与session的summary,其它的Memory的信息通过inner_function访问。但要防止LLM过程中对Inner function的过度使用
2. 在不明确的情况下,如何判断input属于哪个session?(包括是否需要创建新的session)
从上述思考中,得到现在的设计方案:
1. 依旧保留Session,且创建session是明确的用户行为。有一些tunnel根本没有创建session的能力。UI可以用 Lite-LLM来进行辅助的session合并(比如类似GMail的Email归集)。系统可以基于session做“已知信息”的自动加载
2. 在处理Input时,允许使用Agent.Memory的接口来访问更多的Memory的内容。从流程上看,这个过程和访问KB的原理是基本一致的。
3. Self-Thinking的过程中,既要对Session进行整理(得到Session Summary),也要站在更全局的角度对涉及到的Object进行整理(得到Object Summary)。
4. Self-Thinking的过程也是以session为单位的,以更新session-summary为首要目标,并可以在Thiking的过程中,访问已有的object-summary,选择性的更新object-summary
5. Self-Thinking会尽量以时间从新到旧处理所有的原始记录,因此会涉及到对多个Session的Summary的更新。
设计方案的主要风险在于inner function模式可能会带来大量的,无用的object summary查询。
对于UI上不方便创建Session的情况:
1. 通过标题尝试自动创建
2. 通过时间尝试自动创建
3. 既然用户看到的就是一个session,那么我们就必须当一个session来处理
一些推论:
Agent通过一个DB list来访问/写入结构化数据,并拥有自己创建DB的能力
Agent通过一个FileSystem来访问/创建非结构化数据,并拥有理解文件系统组织设计的能力
“不要给Agent直接扩展能力,而是尽量给Agent扩展元能力(读说明书的能力)”
#### Agent.Workspace
#### Agent.behavior
### Workflow
一组Agent共享Work space后的流程
Task可以分配给不同的Agent
Todo的Do和Check可以分配给不同的Agent
## Knowledge Base sisi)
AI First的未来文件系统
## Agent 能力扩展框架
### AI Function / Action
最重要的扩展框架
### Environment
可以通过 {environment.xxx} 读取
### Code Interpreter
Agent 能不能写代码是一个重要的理念之争
能写代码的Agent想象空间大,是通往AGI的必然之路,但不够稳定可预期
不能写代码的Agent可以专注于组合使用基础的能力,稳定可靠的
## AI系统组件
### AI Compute Kernel
通过AI Compute Kernel对 LLM, AIGC等新一代的AI基础能力进行抽象
通过Compute Node可以对这些基础能力进行不同的实现
### AI Models
模型的fine-tune Pipeline
LoRA的Pipeline
### Contact Manage
基于Contact的自然语言权限控制
### Tunnel
可以使用开放API的通信软件,于自己的AI时刻保持沟通
### Spider
持续的导入用户在旧时代的数据。
从Web2->web3
### Calendar (Calendar是否应该是Agent.Worksapce的一部分)
### 基础的pkg_loader
支持一系列可安装的扩展
可扩展的扩展是AIOS的开发者需要重点关注的
Agent (用自然语言扩展)
Workflow (用自然语言扩展)
Plugin:(需要会写代码)
AI Function / Action
Environment
Knowledge Pipeline
LLM Process
Compute Node
### System Config Manage
Zone Config-> System Config
## UI
### Installer
图形化的安装界面,帮助用户能快速的安装使用
我们也会在这里讨论面向用户的AIOS的过渡性安装逻辑
### WebUI & OS Desktop
系统控制面板
Agent/Workflow管理
新Outlook
会话管理 (于Agent会话)
日程管理
Todo管理
新Dropbox
Knowledge Base浏览
Knowledge Base查询
应用商店
### AIOS Shell
### Personal Station (新个人主页)
内容的发布/联系人内容的查看/个人日历的公开
## Frame Service (完全未开始)
通过Frame Service,让AIOS成为一个典型的网络系统(Personal Server OS
这一块会复用很多CYFS/Bucky OS 的基础设计
这一层AI不会直接使用,这一层支持AI系统组件的实现
在用户看来,这一层的功能都是高级的,偏向系统维护的。很少会直接使用
### zone & node-daemon
NOS的booter
### Runtime (Container) Manage
这里抽象了系统的运行时模型
通过容器技术对可扩展组件的权限进行控制,保护系统的隐私安全
### d-Storage & Named Object
Named- Object File System
D-RDB
D-VDB
### BUS
系统消息总线,在不同的系统组件中路由消息
### CYFS (httpv4) Gateway
+18
View File
@@ -0,0 +1,18 @@
# LLMProcess
设计目的是理解到 提示词=>LLM=>LLM Result 的过程是系统的核心复杂度。Agent的粒度太大了,需要更合适的设计来封装这个复杂度,并给予这个过程更大的灵活性和可组合型。并更易于构建测试
比如
1. 可以很容易的组合两个已知的LLM Process(上一个的输出是下一个的输入),这个设计有一点类似LangChain (我们在正式系统中,肯定允许整个Agent都用LangChain来构建)
2. 可以用用一个LLM Process来构建另一个LLM Process的Prompt
3. 继承一个复杂的LLM Process,进行简单配置,就可以得到一个新的LLM Process。这个新的LLM Process可以享受到复杂LLM Process持续迭代的好处
4. 有一些常用的,系统内置的LLM Process可以从配置文件中加载。
```python
def agent.on_process_message():
llm_process = self.on_message_llm_process.clone()
llm_result = llm_process.do()
```
+1
View File
@@ -1,4 +1,5 @@
# OpenDAN Quick Start # OpenDAN Quick Start
OpenDAN (Open and Do Anything Now with AI) is revolutionizing the AI landscape with its Personal AI Operating System. Designed for seamless integration of diverse AI modules, it ensures unmatched interoperability. OpenDAN empowers users to craft powerful AI agents—from butlers and assistants to personal tutors and digital companions—all while retaining control. These agents can team up to tackle complex challenges, integrate with existing services, and command smart(IoT) devices. OpenDAN (Open and Do Anything Now with AI) is revolutionizing the AI landscape with its Personal AI Operating System. Designed for seamless integration of diverse AI modules, it ensures unmatched interoperability. OpenDAN empowers users to craft powerful AI agents—from butlers and assistants to personal tutors and digital companions—all while retaining control. These agents can team up to tackle complex challenges, integrate with existing services, and command smart(IoT) devices.
With OpenDAN, we're putting AI in your hands, making life simpler and smarter. With OpenDAN, we're putting AI in your hands, making life simpler and smarter.
+989
View File
@@ -0,0 +1,989 @@
<mxfile host="Electron" modified="2024-01-29T00:22:46.220Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.1.2 Chrome/114.0.5735.289 Electron/25.9.4 Safari/537.36" etag="XPCvDF8x_5abdhn_GXEm" version="22.1.2" type="device" pages="8">
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
<mxGraphModel dx="2044" dy="1167" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="WIyWlLk6GJQsqaUBKTNV-3" value="(chat) message" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="159" y="330" width="90" height="40" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-0" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-3" target="RbYReh6FQah4Kl5oFyjJ-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="279" y="390" as="sourcePoint" />
<mxPoint x="309" y="350" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-8" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-1" target="RbYReh6FQah4Kl5oFyjJ-2" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-14" value="message" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-1" target="RbYReh6FQah4Kl5oFyjJ-13" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-1" value="Workflow" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6d8764;fontColor=#ffffff;strokeColor=#3A5431;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="304" y="320" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-2" value="Functions" style="shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="514" y="320" width="130" height="60" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-6" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-3" target="RbYReh6FQah4Kl5oFyjJ-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-3" value="(phicyal / virtual)&lt;br&gt;environment" style="shape=cube;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;darkOpacity=0.05;darkOpacity2=0.1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="304" y="120" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-7" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-4" target="RbYReh6FQah4Kl5oFyjJ-1" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-4" value="(event) message" style="rounded=1;whiteSpace=wrap;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="304" y="240" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-9" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0;entryDx=120;entryDy=50;entryPerimeter=0;dashed=1;dashPattern=8 8;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-2" target="RbYReh6FQah4Kl5oFyjJ-3" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="564" y="290" as="sourcePoint" />
<mxPoint x="614" y="240" as="targetPoint" />
<Array as="points">
<mxPoint x="579" y="170" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-10" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.16;entryY=0.55;entryDx=0;entryDy=0;entryPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-2" target="RbYReh6FQah4Kl5oFyjJ-11" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="684" y="320" as="sourcePoint" />
<mxPoint x="714" y="350" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-11" value="Exist Service" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="689" y="300" width="130" height="90" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-13" value="sub / other workflow" style="whiteSpace=wrap;html=1;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="304" y="480" width="120" height="50" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-15" value="response message" style="endArrow=classic;html=1;rounded=0;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-1" target="RbYReh6FQah4Kl5oFyjJ-17" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="129" y="480" as="sourcePoint" />
<mxPoint x="99" y="420" as="targetPoint" />
<Array as="points">
<mxPoint x="334" y="420" />
<mxPoint x="49" y="420" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-18" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-17" target="WIyWlLk6GJQsqaUBKTNV-3" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-17" value="Personal" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="9" y="310" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-19" value="" style="shape=xor;whiteSpace=wrap;html=1;rotation=-90;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="309" y="67" width="20" height="20" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-21" value="" style="shape=xor;whiteSpace=wrap;html=1;rotation=-90;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="347" y="67" width="20" height="20" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-22" value="" style="shape=xor;whiteSpace=wrap;html=1;rotation=-90;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="386" y="67" width="20" height="20" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-23" value="IoT Devices" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="240" y="67" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-25" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.25;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.122;entryY=0.008;entryDx=0;entryDy=0;entryPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-19" target="RbYReh6FQah4Kl5oFyjJ-3" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="240" y="170" as="sourcePoint" />
<mxPoint x="290" y="120" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-26" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.25;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0;entryDx=50;entryDy=0;entryPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-21" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="443" y="120" as="sourcePoint" />
<mxPoint x="357" y="120.00000000000011" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-27" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.25;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.766;entryY=0.02;entryDx=0;entryDy=0;entryPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-22" target="RbYReh6FQah4Kl5oFyjJ-3" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="430" y="140" as="sourcePoint" />
<mxPoint x="480" y="90" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="HvRWRVSjofeyYJQyU3aa-0" value="Sub workflow is not visiable for others&lt;br&gt;&lt;br&gt;Sub worflow can share roles&amp;nbsp;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="160" y="480" width="140" height="70" as="geometry" />
</mxCell>
<mxCell id="HvRWRVSjofeyYJQyU3aa-1" value="Chat with AI Agent is like chat with a workflow which have only one agent and no rules" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="70" y="150" width="150" height="80" as="geometry" />
</mxCell>
<mxCell id="ed6Jjq-fMpksm2tlv9Oi-0" value="inner&amp;nbsp;&lt;br&gt;environment" style="shape=cube;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;darkOpacity=0.05;darkOpacity2=0.1;size=10;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="386" y="360" width="84" height="40" as="geometry" />
</mxCell>
<mxCell id="ed6Jjq-fMpksm2tlv9Oi-1" value="Environment could shared with workflows" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="440" y="120" width="120" height="30" as="geometry" />
</mxCell>
<mxCell id="ed6Jjq-fMpksm2tlv9Oi-2" value="Inner environment&amp;nbsp; could be used as Context(FileSystem)" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="413" y="410" width="110" height="30" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="kWxfmPxtNxOAf0a73TCG" name="Workflow">
<mxGraphModel dx="2046" dy="1168" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="VGzDL9xesOOnN_l_G2Ij-1" value="&lt;h1&gt;Workflow rules&lt;/h1&gt;&lt;p&gt;workflow rules(prompt)&lt;/p&gt;" style="text;html=1;strokeColor=none;fillColor=none;spacing=5;spacingTop=-20;whiteSpace=wrap;overflow=hidden;rounded=0;" parent="1" vertex="1">
<mxGeometry x="70" y="800" width="190" height="120" as="geometry" />
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-2" value="&lt;h1&gt;Workflow roles&lt;/h1&gt;&lt;p&gt;AI Agent groups&lt;/p&gt;&lt;p&gt;Project Manager : Alex&lt;/p&gt;&lt;p&gt;Programer : Bobl&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;" style="text;html=1;strokeColor=none;fillColor=none;spacing=5;spacingTop=-20;whiteSpace=wrap;overflow=hidden;rounded=0;" parent="1" vertex="1">
<mxGeometry x="320" y="790" width="190" height="120" as="geometry" />
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-3" target="VGzDL9xesOOnN_l_G2Ij-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-15" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-3" target="VGzDL9xesOOnN_l_G2Ij-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-3" value="Message" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="340" y="170" width="100" height="40" as="geometry" />
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-4" value="inner filter&amp;nbsp;" style="shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="150" y="260" width="120" height="50" as="geometry" />
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-5" value="roleA" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="80" y="350" width="110" height="60" as="geometry" />
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-6" target="VGzDL9xesOOnN_l_G2Ij-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-6" value="roleB" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="240" y="350" width="110" height="60" as="geometry" />
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-7" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="570.0344827586209" y="350" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-7" value="Workflow rules" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="520" y="260" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-8" value="Final Result of Message" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="340" y="590" width="100" height="50" as="geometry" />
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-9" target="VGzDL9xesOOnN_l_G2Ij-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-9" value="Result Merge" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="521" y="530" width="100" height="40" as="geometry" />
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-10" target="VGzDL9xesOOnN_l_G2Ij-9" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-10" value="roleC" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="435" y="350" width="110" height="60" as="geometry" />
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-11" target="41KxsEdFswCCPQnavNoU-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-11" value="roleD" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="595" y="350" width="110" height="60" as="geometry" />
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-12" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-4" target="VGzDL9xesOOnN_l_G2Ij-5" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="160" y="510" as="sourcePoint" />
<mxPoint x="210" y="460" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-13" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-4" target="VGzDL9xesOOnN_l_G2Ij-6" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="210" y="510" as="sourcePoint" />
<mxPoint x="260" y="460" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-1" value="" style="shape=cross;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="556" y="364" width="30" height="30" as="geometry" />
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="41KxsEdFswCCPQnavNoU-7" target="VGzDL9xesOOnN_l_G2Ij-9" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-7" value="call function or&amp;nbsp;&lt;br&gt;send message" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="592.5" y="460" width="115" height="30" as="geometry" />
</mxCell>
<mxCell id="q_eHDVuD3DBh6_VEKgap-2" value="message process by filter,select one role do LLM and get RESULT of input" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="90" y="190" width="165" height="30" as="geometry" />
</mxCell>
<mxCell id="q_eHDVuD3DBh6_VEKgap-3" value="message process by rules,select mutil role do LLM and merge RESULT of input" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="510" y="180" width="165" height="30" as="geometry" />
</mxCell>
<mxCell id="q_eHDVuD3DBh6_VEKgap-5" value="如果一个工作流有3个role参与,每个人都需要调用函数并分析结果,那么要进行至少6次推理" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="483" y="640" width="174" height="40" as="geometry" />
</mxCell>
<mxCell id="q_eHDVuD3DBh6_VEKgap-6" value="这里是基于规则的合并,给另一个Role合并是多输入Agent流程" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="635" y="570" width="160" height="30" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="7NYJTgo0U9cdVshLy85U" name="Page-3">
<mxGraphModel dx="2046" dy="1168" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="zJVYyOSlNiCVBlA5jEf0-2" value="Agent Sessions&lt;br&gt;Data" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="110" y="533" width="80" height="70" as="geometry" />
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-2" value="Small LLM, Train With Personal Data" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="270" y="258" width="140" height="50" as="geometry" />
</mxCell>
<mxCell id="awKuOLItvF_EOGf5wS2x-4" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="20" y="78" width="160" height="360" as="geometry" />
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-13" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="8StUH8aFnwlRlHm0zShF-1" target="SPnvrlBiOLhgEaWxmdK3-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-1" value="LLM&lt;br&gt;(Common Sence)" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="250" y="218" width="140" height="50" as="geometry" />
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-3" value="ControlNet&lt;br&gt;base on Agent Memory" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="250" y="108" width="140" height="50" as="geometry" />
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-4" value="Agent人格助记词" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;size=0.25;" parent="1" vertex="1">
<mxGeometry x="40" y="108" width="120" height="50" as="geometry" />
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-5" value="Role助记词" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;size=0.25;" parent="1" vertex="1">
<mxGeometry x="40" y="168" width="120" height="50" as="geometry" />
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-6" value="Workflow Rules助记词" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;size=0.25;" parent="1" vertex="1">
<mxGeometry x="40" y="235.5" width="120" height="52.5" as="geometry" />
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-7" value="访问Knowlege构造助记词" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;size=0.25;" parent="1" vertex="1">
<mxGeometry x="40" y="378" width="120" height="50" as="geometry" />
</mxCell>
<mxCell id="Tz-ThZOieDqqDBRXvXnh-1" value="Invoke the function and wait for the necessary response." style="shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="600" y="33" width="110" height="70" as="geometry" />
</mxCell>
<mxCell id="awKuOLItvF_EOGf5wS2x-2" value="Send Message and wait for the necessary response." style="rounded=1;whiteSpace=wrap;html=1;fontSize=11;glass=0;strokeWidth=1;shadow=0;" parent="1" vertex="1">
<mxGeometry x="610" y="153" width="90" height="70" as="geometry" />
</mxCell>
<mxCell id="awKuOLItvF_EOGf5wS2x-5" value="Input Prompts" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="60" y="78" width="80" height="30" as="geometry" />
</mxCell>
<mxCell id="awKuOLItvF_EOGf5wS2x-7" value="Responce Prompts" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="740" y="103" width="80" height="40" as="geometry" />
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="awKuOLItvF_EOGf5wS2x-8" target="awKuOLItvF_EOGf5wS2x-9" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="awKuOLItvF_EOGf5wS2x-8" value="LLM Competion" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="720" y="226.75" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-9" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="awKuOLItvF_EOGf5wS2x-9" target="SPnvrlBiOLhgEaWxmdK3-1" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="awKuOLItvF_EOGf5wS2x-9" value="Final Result&lt;br&gt;Of Message&lt;br&gt;(can be None)" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="450" y="363" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-1" value="Storage Result in agent session and workflow session" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="450" y="483" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-2" value="可用Function助记词" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;size=0.25;" parent="1" vertex="1">
<mxGeometry x="40" y="308" width="120" height="50" as="geometry" />
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-7" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="SPnvrlBiOLhgEaWxmdK3-4" target="SPnvrlBiOLhgEaWxmdK3-6" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-8" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="SPnvrlBiOLhgEaWxmdK3-4" target="awKuOLItvF_EOGf5wS2x-9" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-4" value="推理结果&lt;br&gt;需要进一步处理?" style="rhombus;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="440" y="203" width="140" height="80" as="geometry" />
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-6" value="Call Funstion or send message?" style="rhombus;whiteSpace=wrap;html=1;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="440" y="78" width="140" height="80" as="geometry" />
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-3" value="Call" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="SPnvrlBiOLhgEaWxmdK3-6" target="Tz-ThZOieDqqDBRXvXnh-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="113" as="sourcePoint" />
<mxPoint x="460" y="63" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-4" value="Send" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="SPnvrlBiOLhgEaWxmdK3-6" target="awKuOLItvF_EOGf5wS2x-2" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="560" y="193" as="sourcePoint" />
<mxPoint x="610" y="143" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-5" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="awKuOLItvF_EOGf5wS2x-2" target="awKuOLItvF_EOGf5wS2x-7" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="720" y="213" as="sourcePoint" />
<mxPoint x="770" y="163" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-6" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="Tz-ThZOieDqqDBRXvXnh-1" target="awKuOLItvF_EOGf5wS2x-7" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="750" y="93" as="sourcePoint" />
<mxPoint x="800" y="43" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-7" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="awKuOLItvF_EOGf5wS2x-7" target="awKuOLItvF_EOGf5wS2x-8" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="750" y="213" as="sourcePoint" />
<mxPoint x="800" y="163" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-10" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="330" y="218" as="sourcePoint" />
<mxPoint x="330" y="158" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-12" value="" style="shape=flexArrow;endArrow=classic;html=1;rounded=0;fillColor=#6d8764;strokeColor=#3A5431;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="200" y="251.75" as="sourcePoint" />
<mxPoint x="240" y="252" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-15" value="Real Time Enviroment Knowlege" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="10" y="533" width="80" height="70" as="geometry" />
</mxCell>
<mxCell id="zJVYyOSlNiCVBlA5jEf0-1" value="Workflow Context Data" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="110" y="473" width="80" height="70" as="geometry" />
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-14" value="OOD&#39;s KnowlegeBase" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="10" y="473" width="80" height="70" as="geometry" />
</mxCell>
<mxCell id="zJVYyOSlNiCVBlA5jEf0-3" value="" style="shape=flexArrow;endArrow=classic;html=1;rounded=0;fillColor=#647687;strokeColor=#314354;" parent="1" target="8StUH8aFnwlRlHm0zShF-7" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="100" y="463" as="sourcePoint" />
<mxPoint x="130" y="433" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="iPE2hx-tEKFiySbPAp22-1" value="&lt;font style=&quot;font-size: 11px;&quot;&gt;可用Function受到人格和Workflow的共同影响&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="190" y="318" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="iPE2hx-tEKFiySbPAp22-2" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="SPnvrlBiOLhgEaWxmdK3-2" target="iPE2hx-tEKFiySbPAp22-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="230" y="433" as="sourcePoint" />
<mxPoint x="280" y="383" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="3kOHILldrhYu_NusAgTt" name="LLM Process">
<mxGraphModel dx="1421" dy="816" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="FX_S_WlUCK74q34TItPC-13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="FX_S_WlUCK74q34TItPC-1" target="FX_S_WlUCK74q34TItPC-10" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-1" value="LLM" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="309.97" y="310" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="FX_S_WlUCK74q34TItPC-3" target="FX_S_WlUCK74q34TItPC-1" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-3" value="Request&lt;br&gt;(听指令)" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="99.97" y="310" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="FX_S_WlUCK74q34TItPC-4" target="FX_S_WlUCK74q34TItPC-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-4" value="Workspace&lt;br&gt;(workflow share state)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
<mxGeometry x="309.97" y="100" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-19" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="FX_S_WlUCK74q34TItPC-5" target="FX_S_WlUCK74q34TItPC-1" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="369.97" y="280" />
<mxPoint x="369.97" y="280" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-5" value="Context Prepare&lt;br&gt;(Prompt Generator)" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="309.97" y="220" width="120" height="50" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="FX_S_WlUCK74q34TItPC-10" target="FX_S_WlUCK74q34TItPC-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="FX_S_WlUCK74q34TItPC-10" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="559.97" y="430" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-10" value="LLM Result" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="499.97" y="310" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-11" value="Resp/Say" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="699.97" y="310" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-17" value="OP / Action List&lt;br&gt;(动手)" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;" parent="1" vertex="1">
<mxGeometry x="499.97" y="430" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.75;entryY=0;entryDx=0;entryDy=0;" parent="1" source="FX_S_WlUCK74q34TItPC-20" target="FX_S_WlUCK74q34TItPC-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-20" value="&#xa;&lt;span style=&quot;color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;&quot;&gt;Memory&lt;/span&gt;&lt;br style=&quot;border-color: var(--border-color); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot;&gt;&lt;span style=&quot;color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;&quot;&gt;Chatsession,worklog&lt;/span&gt;&#xa;&#xa;" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
<mxGeometry x="479.97" y="100" width="140" height="80" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.25;entryY=0;entryDx=0;entryDy=0;" parent="1" source="FX_S_WlUCK74q34TItPC-21" target="FX_S_WlUCK74q34TItPC-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-21" value="Knowledge" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
<mxGeometry x="99.97" y="100" width="150" height="80" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;" parent="1" source="FX_S_WlUCK74q34TItPC-25" target="FX_S_WlUCK74q34TItPC-1" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-25" value="Obverse&lt;br&gt;(感受)" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="99.97" y="390" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-30" value="必定改变,Agent可以配置改变模式?" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.855;entryY=1;entryDx=0;entryDy=-4.35;entryPerimeter=0;" parent="1" source="FX_S_WlUCK74q34TItPC-10" target="FX_S_WlUCK74q34TItPC-20" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-33" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="FX_S_WlUCK74q34TItPC-17" target="FX_S_WlUCK74q34TItPC-4" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="49.97" y="480" />
<mxPoint x="49.97" y="80" />
<mxPoint x="369.97" y="80" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-34" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0;entryDx=0;entryDy=52.5;entryPerimeter=0;" parent="1" source="FX_S_WlUCK74q34TItPC-17" target="FX_S_WlUCK74q34TItPC-21" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-36" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" parent="1" source="FX_S_WlUCK74q34TItPC-35" target="FX_S_WlUCK74q34TItPC-1" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="FX_S_WlUCK74q34TItPC-35" value="自驱&lt;br&gt;(自我实现)" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="99.97" y="230" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-1" value="&lt;b&gt;&lt;font style=&quot;font-size: 15px;&quot;&gt;对LLM Inner Function Call的理解:&lt;/font&gt;&lt;/b&gt;&lt;br&gt;1. LLM Function Call 的使用,大多数情况下是Context Prepare的一部分&lt;br&gt;&lt;br&gt;2. 一种在产生Resp前,对OP / Action的结果的立刻检查。不过考虑到Token成本,可能用另一个更简单的LLM 过程来完成检查通常会更合适&lt;br&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="50" y="500" width="360.03" height="180" as="geometry" />
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-2" value="LLM Process&amp;nbsp;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1" parent="1" vertex="1">
<mxGeometry x="345" y="30" width="160" height="30" as="geometry" />
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="mAWNT_NJkWkCqDQJkoOU-3" target="mAWNT_NJkWkCqDQJkoOU-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-3" value="Main Process" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="365" y="880" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-4" value="Prepare Process" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="180" y="880" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="mAWNT_NJkWkCqDQJkoOU-5" target="mAWNT_NJkWkCqDQJkoOU-10" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="720" y="910.2857142857142" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-13" value="Chek Failed" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="mAWNT_NJkWkCqDQJkoOU-5" target="mAWNT_NJkWkCqDQJkoOU-4" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="610" y="850" />
<mxPoint x="240" y="850" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-5" value="Check Process" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="550" y="880" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-6" value="" style="endArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" target="mAWNT_NJkWkCqDQJkoOU-4" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="130" y="910" as="sourcePoint" />
<mxPoint x="160" y="890" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-7" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="mAWNT_NJkWkCqDQJkoOU-4" target="mAWNT_NJkWkCqDQJkoOU-3" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="330" y="860" as="sourcePoint" />
<mxPoint x="380" y="810" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-10" value="Resp/Say" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="719.97" y="880" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-11" value="Request&lt;br&gt;(听指令)" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="10" y="880" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="mAWNT_NJkWkCqDQJkoOU-14" value="Prepare过程可以是固定的计算组合,也可以是LLM参与的" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="380" y="270" width="165" height="30" as="geometry" />
</mxCell>
<mxCell id="f2v0L3gmEFmQxlVvJ0Yg-1" value="&lt;h1&gt;&lt;font style=&quot;font-size: 22px;&quot;&gt;Memory,Env,KB,Workspace的区别&lt;/font&gt;&lt;/h1&gt;&lt;p&gt;Memory是不共享的内部状态,只有 action没有function&lt;br style=&quot;border-color: var(--border-color); font-size: 11px;&quot;&gt;&lt;span style=&quot;font-size: 11px;&quot;&gt;Workspace是共享的状态,主要是action&lt;/span&gt;&lt;br style=&quot;border-color: var(--border-color); font-size: 11px;&quot;&gt;&lt;span style=&quot;font-size: 11px;&quot;&gt;Env是全局状态,主要是inner_function,有的有小部分action&lt;/span&gt;&lt;br style=&quot;border-color: var(--border-color); font-size: 11px;&quot;&gt;&lt;span style=&quot;font-size: 11px;&quot;&gt;Knowledge基本是只读的,除非专门整理Knowledge的Agent,只有function&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;" style="text;html=1;strokeColor=none;fillColor=none;spacing=5;spacingTop=-20;whiteSpace=wrap;overflow=hidden;rounded=0;" parent="1" vertex="1">
<mxGeometry x="449.97" y="530" width="390" height="230" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="CqH4P1G_RAjWfIoYkaMU" name="Task/Todo">
<mxGraphModel dx="2044" dy="1167" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-1" target="Po7mxAQLMo4zYcBtAZ7U-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-1" value="未开始的Task" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="380" y="40" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-19" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-2" target="Po7mxAQLMo4zYcBtAZ7U-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-2" value="确定的Task&lt;br&gt;确定的执行时间(在这之前和之后都不会执行),&lt;br&gt;确定的执行人" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="570" y="250" width="120" height="140" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-15" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-5" target="Po7mxAQLMo4zYcBtAZ7U-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-5" target="Po7mxAQLMo4zYcBtAZ7U-2" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-32" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-5" target="Po7mxAQLMo4zYcBtAZ7U-31" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-5" value="Task Review" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="380" y="140" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-6" target="Po7mxAQLMo4zYcBtAZ7U-2" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-6" value="Quick Review" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="380" y="290" width="130" height="60" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-7" target="Po7mxAQLMo4zYcBtAZ7U-6" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-7" value="半确定的Task&lt;br&gt;ASSIGNED&lt;br&gt;确定的执行人&lt;br&gt;不确定的执行时间(和条件有关)" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="210" y="250" width="120" height="140" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-8" target="Po7mxAQLMo4zYcBtAZ7U-9" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-8" value="Task Review" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="570" y="430" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-9" target="Po7mxAQLMo4zYcBtAZ7U-10" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-9" value="未执行Todo" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="570" y="530" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-10" target="Po7mxAQLMo4zYcBtAZ7U-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-10" value="Do Todo" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="570" y="640" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-11" target="Po7mxAQLMo4zYcBtAZ7U-12" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-11" value="已执行Todo" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="570" y="760" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-12" target="Po7mxAQLMo4zYcBtAZ7U-24" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-12" value="Check Todo" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="570" y="860" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-27" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-24" target="Po7mxAQLMo4zYcBtAZ7U-26" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-24" value="已完成TODO" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="570" y="960" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-29" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-26" target="Po7mxAQLMo4zYcBtAZ7U-28" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-26" value="Check Task" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="360" y="960" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-43" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="Po7mxAQLMo4zYcBtAZ7U-28" target="Po7mxAQLMo4zYcBtAZ7U-5" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="90" y="990" />
<mxPoint x="90" y="170" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-28" value="已完成的Task" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="180" y="960" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-31" value="未开始的SubTask(通常是半确定的)" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="560" y="140" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-35" value="Plan" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="470" y="130" width="50" height="20" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-36" value="Plan" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="660" y="420" width="50" height="20" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-37" value="将Task拆解成一系列TODO" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="640" y="490" width="155" height="30" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-38" value="Check" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="470" y="280" width="50" height="20" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-40" value="Check" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="650" y="850" width="50" height="20" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-41" value="Check" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="440" y="950" width="50" height="20" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-42" value="Do" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="660" y="630" width="50" height="20" as="geometry" />
</mxCell>
<mxCell id="Po7mxAQLMo4zYcBtAZ7U-44" value="失败的有机会重新开始" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="110" y="950" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="JyX1mhbkDWa9bGLsmIIT-1" value="创建" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="500" y="170" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="dTeZOjKiqq0ditQk53ut-1" value="&lt;h1&gt;防止无限拆分&lt;/h1&gt;&lt;p&gt;大部分情况下,我们都鼓励使用&lt;/p&gt;&lt;p&gt;Task-Todo-Action这种3层结构来完成任务。或则再多一层Task-SubTask-Todo-Action. 今天LLM能面对的复杂度应该就是最多这4层了&lt;/p&gt;" style="text;html=1;strokeColor=none;fillColor=none;spacing=5;spacingTop=-20;whiteSpace=wrap;overflow=hidden;rounded=0;" parent="1" vertex="1">
<mxGeometry x="190" y="470" width="190" height="240" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="1AT3sDmhgRXqQU36RNgs" name="New Workflow">
<mxGraphModel dx="2044" dy="1167" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="h0utY9AjMI-hVYMkH-L3-1" value="&lt;h1&gt;Workflow的核心设计&lt;/h1&gt;&lt;p&gt;1.工作流定义,工作流指导了workflow中特定的task是如何交给不同的agent完成的&lt;/p&gt;&lt;p&gt;2.agent在workflow中工作时,自己的llm_process会受到影响,有时可以使用workflow中的一些额定义的llm_process&lt;/p&gt;&lt;p&gt;3.workflow的状态是独立的,主要是指&lt;/p&gt;&lt;p&gt;1.独立的workspace&lt;/p&gt;&lt;p&gt;该workspace里支持公共的log保存&lt;/p&gt;&lt;p&gt;2.独立的env&lt;/p&gt;&lt;p&gt;3.独立的kb&lt;/p&gt;&lt;p&gt;4.Agent依旧使用自己的memory+&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;" style="text;html=1;strokeColor=none;fillColor=none;spacing=5;spacingTop=-20;whiteSpace=wrap;overflow=hidden;rounded=0;" parent="1" vertex="1">
<mxGeometry x="70" y="50" width="300" height="330" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="EohhKbyxIwDpnJuhpjKB" name="Task/Todo state machine">
<mxGraphModel dx="2894" dy="1167" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="9UtHOai_7pWWWwvgcpnR-50" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-1" target="9UtHOai_7pWWWwvgcpnR-2" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-1" value="TaskList" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="55" y="210" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-2" value="Triage&lt;br&gt;统一的处理最近刚创建的任务" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="235" y="210" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-3" value="简单任务直接完成" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="475" y="100" width="190" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-70" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-5" target="9UtHOai_7pWWWwvgcpnR-6" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="580" y="405" />
<mxPoint x="-100" y="405" />
<mxPoint x="-100" y="555" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-5" value="确认任务:下一次检查时间,调整优先级&lt;br&gt;CONFIROM" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="445" y="310" width="260" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-51" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-6" target="9UtHOai_7pWWWwvgcpnR-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-6" value="Task" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="70" y="540" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="n17Tc1c1lNDFk3IK2LSY-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-7" target="9UtHOai_7pWWWwvgcpnR-9" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-7" value="Plan" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="250" y="540" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-66" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-8" target="9UtHOai_7pWWWwvgcpnR-27" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="555" y="430" />
<mxPoint x="-40" y="430" />
<mxPoint x="-40" y="1420" />
<mxPoint x="129" y="1420" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-8" value="简单任务直接完成" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="460" y="450" width="190" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-9" value="直接拒绝任务" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="450" y="540" width="190" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-10" value="直接拒绝任务" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="475" y="200" width="190" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-11" target="9UtHOai_7pWWWwvgcpnR-15" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="535" y="750" />
<mxPoint x="50" y="750" />
<mxPoint x="50" y="920" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-11" value="DOING&lt;br&gt;拆解子任务 or 拆解TODO" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="440" y="650" width="190" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-13" value="Task Logs&lt;br&gt;父任务,兄弟任务,Todos" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="250" y="450" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-52" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-15" target="9UtHOai_7pWWWwvgcpnR-16" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-15" value="Todo" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="70" y="890" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-55" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-16" target="9UtHOai_7pWWWwvgcpnR-20" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-16" value="DO" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="240" y="890" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-19" target="9UtHOai_7pWWWwvgcpnR-21" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-19" value="执行成功" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="430" y="960" width="190" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-20" target="9UtHOai_7pWWWwvgcpnR-15" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="535" y="790" />
<mxPoint x="130" y="790" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-20" value="执行失败" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="440" y="840" width="190" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-53" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-21" target="9UtHOai_7pWWWwvgcpnR-22" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-21" value="Todo" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="70" y="1120" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-59" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-22" target="9UtHOai_7pWWWwvgcpnR-25" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-60" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-22" target="9UtHOai_7pWWWwvgcpnR-26" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-22" value="Check&lt;br&gt;(解决幻视)" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="240" y="1120" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-25" value="检查失败" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="430" y="1090" width="190" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-28" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-26" target="9UtHOai_7pWWWwvgcpnR-27" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-26" value="检查成功" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="425" y="1180" width="190" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-54" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-27" target="9UtHOai_7pWWWwvgcpnR-29" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-27" value="有TODOS或有SubTask的Task。注意Review是顺序的" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="69" y="1340" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-67" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-29" target="9UtHOai_7pWWWwvgcpnR-30" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-29" value="Review" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="240" y="1340" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-30" value="Task 完成" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="425" y="1320" width="190" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-32" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-31" target="9UtHOai_7pWWWwvgcpnR-6" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="520" y="1580" />
<mxPoint x="-100" y="1580" />
<mxPoint x="-100" y="570" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-31" value="Task未完成" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="425" y="1500" width="190" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-33" value="所有确认了但未完成的任务都有可能被Plan" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="69" y="480" width="120" height="45" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-36" value="Task 最终失败" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="425" y="1410" width="190" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-37" value="Task Logs&lt;br&gt;父任务,兄弟TODOs" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="240" y="810" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-38" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0.422;entryY=0.972;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-11" target="9UtHOai_7pWWWwvgcpnR-6" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-39" value="未定义依赖关系的subtask可以并行执行" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="185" y="650" width="110" height="30" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-40" value="TODO按顺序执行" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="40" y="720" width="110" height="30" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-46" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0.035;entryY=0.361;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-2" target="9UtHOai_7pWWWwvgcpnR-3" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="385" y="220" as="sourcePoint" />
<mxPoint x="435" y="170" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-47" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-2" target="9UtHOai_7pWWWwvgcpnR-10" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="375" y="270" as="sourcePoint" />
<mxPoint x="472" y="210" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-49" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-2" target="9UtHOai_7pWWWwvgcpnR-5" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="325" y="370" as="sourcePoint" />
<mxPoint x="375" y="320" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-58" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-16" target="9UtHOai_7pWWWwvgcpnR-19" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="300" y="1050" as="sourcePoint" />
<mxPoint x="350" y="1000" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-61" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=0;exitDx=0;exitDy=0;entryX=0.428;entryY=1.061;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-25" target="9UtHOai_7pWWWwvgcpnR-15" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="478" y="1050" />
<mxPoint x="121" y="1050" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-62" value="满足Review条件后" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="55" y="1290" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-65" value="刚刚创建的任务通常保持其原始文本描述" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="235" y="280" width="120" height="30" as="geometry" />
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-68" value="" style="endArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.617;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-29" target="9UtHOai_7pWWWwvgcpnR-36" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="300" y="1490" as="sourcePoint" />
<mxPoint x="350" y="1440" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="9UtHOai_7pWWWwvgcpnR-69" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-29" target="9UtHOai_7pWWWwvgcpnR-31" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="330" y="1490" as="sourcePoint" />
<mxPoint x="380" y="1440" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="n17Tc1c1lNDFk3IK2LSY-3" value="" style="endArrow=classic;html=1;rounded=0;exitX=1.003;exitY=0.147;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-7" target="9UtHOai_7pWWWwvgcpnR-8" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="390" y="530" as="sourcePoint" />
<mxPoint x="440" y="480" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="n17Tc1c1lNDFk3IK2LSY-4" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" parent="1" source="9UtHOai_7pWWWwvgcpnR-7" target="9UtHOai_7pWWWwvgcpnR-11" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="380" y="670" as="sourcePoint" />
<mxPoint x="430" y="620" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="7rhNWbdtJh7hNxC4Nqiq" name="Page-8">
<mxGraphModel dx="1168" dy="667" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="5sM_x3XBksQVRvh5ABVG-1" value="LLM Process" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="410" y="270" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-2" value="Input" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" vertex="1" parent="1">
<mxGeometry x="305" y="80" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-3" value="Output" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" vertex="1" parent="1">
<mxGeometry x="520" y="80" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="5sM_x3XBksQVRvh5ABVG-4" target="5sM_x3XBksQVRvh5ABVG-21">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-4" value="Actions" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="530" y="440" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-5" value="Functions" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="300" y="440" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="5sM_x3XBksQVRvh5ABVG-6" target="5sM_x3XBksQVRvh5ABVG-5">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-6" value="显性的,有组织的记忆(表层意识)&lt;br&gt;基于Storage" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
<mxGeometry x="60" y="360" width="120" height="120" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;" edge="1" parent="1" source="5sM_x3XBksQVRvh5ABVG-7" target="5sM_x3XBksQVRvh5ABVG-5">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-7" value="知识库" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
<mxGeometry x="50" y="530" width="120" height="120" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-8" value="潜意识&lt;br&gt;涌现的直觉性能力&lt;br&gt;(基于神经元网络))" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="80" y="240" width="160" height="100" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-10" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.446;exitY=1.02;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="5sM_x3XBksQVRvh5ABVG-2" target="5sM_x3XBksQVRvh5ABVG-1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="350" y="230" as="sourcePoint" />
<mxPoint x="400" y="180" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-11" value="" style="endArrow=classic;html=1;rounded=0;entryX=0.463;entryY=1.013;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" target="5sM_x3XBksQVRvh5ABVG-3">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="470" y="270" as="sourcePoint" />
<mxPoint x="520" y="220" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-12" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.526;entryY=-0.06;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="5sM_x3XBksQVRvh5ABVG-1" target="5sM_x3XBksQVRvh5ABVG-4">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="540" y="420" as="sourcePoint" />
<mxPoint x="590" y="370" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-13" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="5sM_x3XBksQVRvh5ABVG-5" target="5sM_x3XBksQVRvh5ABVG-1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="360" y="430" as="sourcePoint" />
<mxPoint x="410" y="380" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-15" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="5sM_x3XBksQVRvh5ABVG-14" target="5sM_x3XBksQVRvh5ABVG-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-14" value="Context" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="230" y="190" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-19" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="5sM_x3XBksQVRvh5ABVG-4" target="5sM_x3XBksQVRvh5ABVG-7">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="590" y="670" />
<mxPoint x="110" y="670" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="5sM_x3XBksQVRvh5ABVG-4" target="5sM_x3XBksQVRvh5ABVG-6">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="620" y="710" />
<mxPoint x="40" y="710" />
<mxPoint x="40" y="420" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-21" value="真实世界" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="730" y="430" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="5sM_x3XBksQVRvh5ABVG-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.96;exitY=0.7;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.669;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="5sM_x3XBksQVRvh5ABVG-8" target="5sM_x3XBksQVRvh5ABVG-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
+273
View File
@@ -0,0 +1,273 @@
# AI Function list (Local)
根据新的Function/Action 定义,我们需要记录系统目前提供的所有注册到GlobaToolsLibrary里的function,方便配置组合
功能扩展只需要扩展function就好了,Action可以通过Function直接得到。
本文档提到的AI Function一般都是Local Function, 基于这个架构,我们可以用Web3的思路整合AI Service。
## Context Prepare
很多AIFUnction的正确调用都需要在参数里提供一个状态上下文。这个上下文的准备一般由LLProcess完成.而可以通过提示词实时改变的参数是AIFunction的真参数。目前需要做Context Prepare的主要和Agent相关的基础函数。
## Agent Core
Agent Core中提供的,与Agent/Workflow 状态关联的基础函数,是Agent的核心设计。对Agent来说,这些基础能力通常都是打开的,我们需要非常谨慎的思考哪些功能应该放到Agent Core中。
根据我们的架构设计,Agent Core的函数包含 Agent的Memory + TODO能力(Plan)。通过这些基础函数,支持了Agent的
- Process Message
- Planing: 让Agent可以基于Zone的权限Process Task/Todo
- Self-Think: Process的经验进行总结
- Self-Improve: 对Agent的提示词进行持续的自我改进。
### logs
logs通常代表Agent的短期记忆,目前有两种Log:chatlog和worklog
目前我们并不鼓励Agent提供短期记忆的调整能力,都是通过标准流程得到一定实践范围的的,完整的短期记忆。
set_message //增加tag
get_chatlogs
get_worklogs
set_worklogs //增加tag
### Memory
Memory 代表Agent的长期记忆
通过LLM驱动的Self-Think流程来更新。基本上是在根据短期记忆提炼对人、对事的看法和终结。也包含一些基于提示词工程的自我能力提升(比如复用已知的,好用的工具,或则复用自己为了解决某个特定问题已经制作并使用成功的工具)
get_contact_summary
update_contact_summary
get_sth_summary
update_sth_summary
### Workspace (TODOList)
Workspace为Agent提供了可以完成TODO的工具和保存工作结果的状态空间(FileSystem)。每个Agent默认有自己的private workspaceWorkflow为Agent提供了可以共享的workspaceWorkspace尽量基于文件系统构造,也方便Agent与人类协同工作。
#### Task/Todo 管理
context中需要一个隐藏的_workspace
```
_self = parameters.get("_workspace")
```
##### agent.workspace.list_task
##### agent.workspace.create_task
```json
{
"title": {"type": "string", "description": "The title of the task."},
"detail": {"type": "string", "description": "The detail of the task."},
"tags": {"type": "string array", "description": "The tags of the task."},
"due_date": {"type": "string", "description": "The due date of the task."},
"parent_id": {"type": "string", "description": "The parent id of the task."},
}
```
##### agent.workspace.cancel_task
```json
{
"task_id": {"type": "string", "description": "The id of the task to cancel."},
}
```
##### agent.workspace.update_task
##### agent.workspace.get_sub_tasks
##### agent.workspace.create_todos
##### agent.workspace.list_todos
##### agent.workspace.get_todo
##### agent.workspace.update_todo
#### 核心的完成TODO的能力
##### Code Interprete
##### Send Msg (系统原生能力)
##### Workspace File System
agent.workspace.write_file
agent.workspace.read_file
agent.workspace.delte_file
agent.workspace.append_file
agent.workspace.list_file
## Knowledge Base
Knowledge Base对大部分Agent来说,是一个获得私有信息,并让LLM处理结果更好的基础设施(RAG支持)。少部分Agent会使用相关API,结合Knowledge Base所服务的目标来整理Knowledge Base.
### 搜索
#### 矢量搜索
#### 传统的文本搜索搜索
#### 根据已经存在的数据库描述,构造SQL搜索
### 当成文件系统浏览
## AIGC
一系列AIGC函数,是LLM打通AIGC能力,形成新生产力的基础。
### aigc.text_2_image
文生图.返回的是生成图片的路径。
```json
{
"prompt": "Description of the content of the painting",
}
```
### aigc.image_2_text
图生文,返回的是图片的描述
(TODO:是否需要有一个提示词来要求针对特定问题对图片进行描述)
```json
{
"image_path": {"type": "string", "description": "image file path"}
}
```
### aigc.voice_to_text
```json
{
"audio_file": {"type": "string", "description": "Audio file path"},
"model": {"type": "string", "description": "Recognition model", "enum": ["openai-whisper"]},
"prompt": {"type": "string", "description": "Prompt statement, can be None"},
"response_format": {"type": "string", "description": "Return format", "enum": ["text", "json", "srt", "verbose_json", "vtt"]},
}
```
## system
访问当前系统的基础设施
### system.now
返回当前时间
### system.calender
保存在当前Zone上的日历,默认是与Zone Owner相关的。也可以以自动形式同步别人的日历
这个组件对AI成为个人助理非常重要,当与旧世界互通时,其细节的复杂度也是非常高的。
这是一个典型的看起来容易做起来难度很大的基础组件,是思考和验证LLM对传统软件复杂度进行降维的一个关键实践。
#### system.calender.get_events
#### system.calender.add_event
#### system.calender.delete_event
#### system.calender.update_event
### system.contacts
访问用户的联系人列表。在OOD System 中,联系人列表是非常重要的系统基础设施,为一系列的权限控制提供了基础信息。
#### system.contacts.get
通过contanct的名字得到contact的完整信息(json格式)
```json
{"name":"name"}
```
#### system.contacts.set
设置 contact 信息,注意这里使用了一个可扩展结构,我们可能需要定义一些标准的必填信息。
```json
{"name":"name","contact_info":"A json to descrpit contact"}
```
### System.shell
#### system.shell.exec
执行Shell命令(目前只支持Linux Bash
## web
访问web的函数。使用下列函数要确保Agent有访问互联网的权限。
### web.search.duckduckgo
使用搜索引擎搜索互联网
```json
{
"query": {"type": "string", "description": "The query to search for."}
}
```
## 常见的llm_context(能力分组)
为典型的LLM处理过程,进行了分组。主要是为了节约Token。
使用Action比使用inner function更节约。
### Process消息组 (定制度高)
得到潜在的Task并创建,在这个过程中可能需要查询已有的任务(防止重复创建)
action:craete_task, function:list_task
通常Agent在Process Message时,会表现出和其处理TODO接近的能力,核心的区别在于Process Message是立刻处理并给出结果,而变成Task更多的是当成一个异步的任务
为了能处理回复,查询历史沟通记录(寻找记忆的过程)
为了能处理回复,查询KB的过程
REMARK:目前LLM的主要问题是,如果开放的function, LLM会倾向于优先使用,是否可以做成“如果用户对答案不满意”,再使用?
### Review Task
对Task的首次执行,Review的目的
- 拆分创建TODO(使用create_todos
- 对简单的任务立刻执行并记录更新结果 (通常是)
- 认为超出自己的能力范围,标记为无法处理或转交给合适的人(Agent (使用update_task)
- 对已有任务进行查询(list_task,query_task
### Do TODO(定制度高)
DO行为是复杂的,我们会精细的区分TODO的首次执行和失败后再次执行。失败后再次执行会得到之前的记录摘要,并有查询之前工作日志的能力。
Do TODO是Agent的另一个核心行为,这里会根据Agent的设定,集成更多的能力
系统为Do提供的默认支持:(按难度逐步增加)
- 写文档 : 不需要任何外部支持
- 运行AIGC AIGC的函数组
- 收集,整理信息(通过互联网或查询知识库): web.search.duckduckgo
- 发送消息,系统自带,但可能需要依赖一些通讯录浏览/查找函数
- 执行自己的代码/编写代码并执行 system.shell.execcode_interprete (这是一个重点模块!)
- 运行网络服务 :智能合约的有通用套路,非智能合约的需要有一个完整的SOP来支持
根据任务要求保存工作成功是手动的,这里有一组workspace级别的文件系统API。
保存工作记录的行为是自动的,默认所有的Action都执行成就算是DoComplte,会自动的更新状态
有的Do可能需要自我迭代一下,这和大多数Behavor只有一次LLM调用有所不同。
### Check TODO/Task
为了解决LLM不可避免的幻视加入的Check流程。该流程会根据TODO的目标,对TODO的结果进行判定
使用 update_todo 更新TODO的状态
当所有的sub_todos都完成后,会check task的目标是否达到
当所有的sub_task都完成后,会check task的目标是否达到
因此,提供的函数主要是得到 todo/task的更深入细节的函数(访问相关log),已经读取相关 工程成果文件 的函数
### Self-Think
获得logs 和summary
进行update
### Learning
得到logs和summary
浏览和整理KB
### Self-Improve
+87
View File
@@ -0,0 +1,87 @@
# 配置knowledge pipeline
knowledge pipeline 扫描指定的输入,把输入的内容构建为结构化的knowledge object,之后依照使用knowledge的应用场景,为object创建各种各样的索引。
## Input
输入定义从个人数据来源转换成结构化的knowledge object的过程,并且定义在object上调用parser的粒度。比如典型的几种Input的实现:
+ 本地目录:指定本地目录,扫描本地目录的所有文件,并且监听他的更新;对每个一个文件生成object并且写入object store;对每一个新产生的object调用parser
+ 个人邮箱:扫描个人邮箱收件箱,并且监听新的邮件;对每一封邮件生成email object并且写入object store;对每一个新产生的email object调用parser
+ 浏览器上下文:实现浏览器插件,对当前浏览的页面元素通过rpc传入对应的input 后端实现,生成rich text object;对每一个新产生的rich text object调用parser
## Parser
Parser定义从input 输入的object 创建索引的过程;包括但不限于以下主要手段,以及他们的组合:
+ 向量化之后写入vector store
+ 创建各种维度的RDB,NoSQL索引
+ 向Agent send object
配置Pipeline 应当包含以下几个部分:
+ Input method:包含实现input的 python module
+ Input paramsinpu module的参数,比如本地路径,邮箱地址
+ Parser method:包含实现parser的 python module;如果Parser是指向Agent,这个配置是可以简化成Agent instance name
# Knowledge pipeline manager
pipeline 管理会类似agent managermanager管理pipeline config,从config 创建instance在后台持续运行, knowledge pipeline manager 也需要处理pipeline instance的状态管理.
集成到aios shell中,加入如下命令:
+ knowledge pipelines 返回当前运行中的pipeline实例
+ knowledge journal $pipeline [$topn] 查询当前pipeline运行的journal日志
+ knowledge query $object_id: 查询指定knowledge object的内容
# 在aios shell中添加新的knowledge pipeline
在$home/myai/knowledge_pipelines/, 或者开发模式下在 $source_root/rootfs/knowledge_pipelines/ 目录中,添加新的pipeline 目录, 以下以内建的pipeline Mia为例说明:
## pipeline.toml
创建pipeline.toml配置文件
+ name字段指定全局唯一的pipeline name
+ input.module字段指向相对pipeline目录的input实现
+ input.params字段定义input的输入参数,不同的input实现可以有不同的参数格式
+ parser 部分也是类似
``` toml
name = "Mia"
input.module = "input.py"
input.params.path = "${myai_dir}/data"
parser.module = "parser.py"
parser.params.path = "${myai_dir}/knowledge/indices/embedding"
```
## input
input模块至少应当实现:
```python
async def next(self):
```
定义input class,实现异步迭代生成器方法next,扫描输入,对其中的每一个元素生成结构化的knowledge object
+ 如果input中的所有元素都扫描完成了,返回None, pipeline会被标记为finish
+ 如果input可pending,等待新的输入,返回(None, None)
+ 如果要把创建的object传递到parser,返回(object_id, journal_str,其中journal_str是产生的journal 日志中的input 部分;
Mia中的实现就是扫描目录中的文件,对文本和图片创建object;
```python
def init(env: KnowledgePipelineEnvironment, params: dict)
```
创建input class的实例并返回
## parser
parser模块至少应当实现
```python
async def parse(self, object: ObjectID) -> str:
```
定义parser class,实现parse成员方法,对input中返回的object_id创建索引,返回journal_str.
Mia中的实现就是对输入的object内容embedding,并且保存到chromadb中;
```python
def init(env: KnowledgePipelineEnvironment, params: dict)
```
创建parser class的实例并返回
# 使用pipeline创建的索引
pipeline定义了创建knowledge object 和索引的过程,对应的要使用pipeline创建的索引完成工作。
还是以内建的Mia为例,不止创建名为Mia的pipeline,还在Agent中加入了查询Mia pipeline创建出来的chromadb的 Agent Mia
## query.py
query 模块并不是pipeline的一部分,其逻辑是跟parser是一致的,在query中定义了一个agent可访问的query function,输入prompt,返回chromadb中embedding相近的object id
## agent.toml
```toml
owner_env = "../../knowledge_pipelines/Mia/query.py"
```
在Mia的agent template配置里,引用query模块创建的query function;并且编辑好让Mia推理调用query方法的提示词。
+30
View File
@@ -0,0 +1,30 @@
# learn todo list
在workspace中分离出独立的两个work list;用于处理工作的work todo list 和用于更新knowledge的 learn todo list;两种todo list在agent templete中单独的配置prompt,每个todo list都可以配置 do , check 和 review 三个prompt。
learn todo list中的todo,可以在knowledge pipeline 生成。
一个典型的例子是新的JarvisPlus agent,它可以依照自己的理解将某个本地目录中的文档归类到不同的逻辑目录中:
+ 首先定义他的pipeline:扫描目录中没有读过的文档,传递给parser;
+ 定义他的pipeline parser的实现:向自己所处的workspace 的learn todo list中添加一条todo todo的内容是文档的全文;
+ 定义他的 learn prompts:根据输入的文档内容,输出摘要,和归类后的目录,产生一组归类operation;
+ workspace中嵌入了支持归类operation 的knowledage base environment,可以执行learn todo list产生的归类operation
# environments and workspace
agent在独立的workspace中工作,目前默认的workspace是agent自己;除了自己的workspaceagent也可以进入其他的workspaceenvironment表示agent可以调用的function,和可以产生的operation
environment中的function或者operation输出的结果,应当应用在agent所处的workspace中,所以agent在不同workspace中执行work,结果应当是被隔离的。
# Learn Todo List
Separate two independent work lists in the workspace: a work todo list for handling work, and a learn todo list for updating knowledge. Both types of todo lists have their own configured prompts in the agent template, and each todo list can configure do, check, and review prompts.
The todos in the learn todo list can be generated in the knowledge pipeline.
A typical example is the new JarvisPlus agent, which can categorize documents from a local directory into different logical directories according to its understanding:
+ First, define its pipeline: scan documents in the directory that have not been read and pass them to the parser;
+ Define the implementation of its pipeline parser: add a todo to the learn todo list of the workspace it is in, the content of the todo is the full text of the document;
+ Define its learn prompts: based on the input document content, output a summary and the directory after categorization, generating a set of categorization operations;
+ The workspace embeds a knowledge base environment that supports categorization operations, which can execute the categorization operations generated by the learn todo list.
# Environments and Workspace
The agent works in an independent workspace, and the current default workspace is the agent itself. In addition to its own workspace, the agent can also enter other workspaces. The environment represents the functions that the agent can call and the operations it can generate.
The results of the functions or operations in the environment should be applied in the workspace where the agent is located, so the results of the agent's work in different workspaces should be isolated.
Please note that the translation might not be perfect due to the technical nature of the text and potential ambiguity in the original text.
+193
View File
@@ -0,0 +1,193 @@
# Proposal for Adjusting the Goals for Version 0.5.2
Dear Team,
Given the recent launch of OpenAI's new version in early November 2023, many of us may have felt a profound shift in the industry. As the world changes, I believe we should adapt accordingly. Here are some of my thoughts:
1. **Affirmation of Our Path**: OpenAI's latest release, particularly the functionalities of the so-called GPTs Agent platform, is largely similar to our 0.5.1 version released on September 28th. This strongly affirms the correctness of our direction. OpenAI has done a great job educating the market about the Agent, so we no longer need to emphasize the correct use of LLM based on the Agent through version releases. This part of user education product design can be simplified.
2. **Innovation in Version 0.5.2**: For our new version (0.5.2), besides maintaining the combinational advantages brought by private deployment of LLM, I believe we need to implement some of the innovative ideas we've discussed about the Agent. This is crucial to maintaining our leading position and avoiding the impression that OpenDAN is merely a follower of GPTs.
3. **Integration of OpenAI's New Capabilities**: We should fully integrate the new capabilities brought by OpenAI's latest release, especially the longer Token Windows, GPT-V, and Code-interpreter. I believe these new features can effectively solve some known issues.
Therefore, I propose to adjust the goals and plans for version 0.5.2. Here are the core objectives:
- Aim to release version 0.5.2 by the end of November, focusing on:
- Launching a new Agent with Autonomous capabilities and multi-Agent collaboration based on Workspace.
- The integrated product of 0.5.2 will be a private deployment email analysis Agent for small and medium-sized enterprises. This will allow any company to better support its CEO and other management positions through LLM while ensuring privacy and security.
- (Optional) By combining LLM and AIGC, build an Agent-based personalized AIGC application, such as a "children's audio picture book" generator that includes both text-to-image and text-to-sound.
- (Optional) Through multi-Agent collaboration, fully utilize the capabilities of GPT4-Turbo, and attempt to let AI and engineers collaborate on research and development tasks based on Git.
The detailed version plan is as follows:
## MVP plan adjustment
In order to keep the list below too long, the system distributed version is 0.5.3, I think we will open another ISSUE discussion and record, this list does not include.
The modules that are not specially explained are components completed in the 0.5.2 plan
- [x] AIOS Kernel
- [x] Basic Agent,@waterflier, A2
- [x] Python Agent Extend, @wugren, S2
- [x] Basic Workflow,@waterflier, A2
- [ ] Workflow Refactor,@waterflier, A2
- [x] AI Functions,@waterflier,A2
- [ ] Upgrade to GPT4 tools API,S2
- [x] AI Environments,@waterflier, A2
- [x] Celender Environment,@waterflier, S2
- [x] Contanct Manage Support,@waterflier, S2
- [x] AI Shell Enviroment,@waterflier, S1
- [x] Upgrade Agent Working Cycle
- [x] Process Message,@waterflier,A2
- [x] Process Group Message,@waterflier,A2
- [ ] Process Event,(0.5.3)
- [ ] Completion of self-drive,@waterflier,A4
- [ ] Self-learn,@weaterflier,A2
- [ ] Introspection,@waterflier,A2
- [ ] Workspace Environment
- [ ] Task/TODO Manager,@waterflier, A2
- [x] Local File System,@waterflier, S1
- [ ] Web Search, A4
- [ ] Code Interpeter (The first implementation can be based on Openai), A4
- [ ] Query SQL DB, S1
- [x] AI BUS,@waterflier, A2
- [ ] Agent Message MIME Support (Image,Video,Audio), A3
- [x] Connect to Human, @waterflier,A2
- [x] Chatsession,@waterflier, S2
- [ ] Compress Chatsession By Text Summary, @waterflier, A2
- [ ] Knowlege Base,@lurenpluto ,@photosssa
- [x] Knowledge Base Frame,@photosssa,A3
- [x] Knowledge Base Object Store,@lurenpluto ,A4
- [x] Knowledge Base Basic Pipline,@photosssa ,A3
- [ ] Customize pipeline,@photosss,A4
- [ ] Support Local Text Search,A4
- [x] Text Summary Pipline,@waterflier, A2
- [ ] Text Parser Support
- [x] PDF Parser,@waterflier,S2
- [ ] doc Parser,S4
- [x] MD Parser,@waterflier,S1
- [ ] Source Code Parser,S4
- [ ] Image Parser (Base on GPT-V),(S2)
- [ ] Video Parser (Base on GPT-V), (S4)
- [ ] Personal AIGC Models
- [ ] Stable Diffusion Controler Agent (Optional),A6
- [x] AI Compute System,@waterflier, A2
- [x] Scheduler,@streetycat, A2
- [x] LLM Kernel
- [x] GPT4 (Cloud),@waterflier, S1
- [x] LLaMa2,@streetycat, A2
- [ ] Claude2, S2
- [ ] Falcon2, S2
- [ ] MPT-7B, S2
- [ ] Vicuna, S2
- [x] Embeding, @lurenpluto , A4
- [x] Txt2img,@glen0125,A4
- [ ] Support DALL-e, @glen0125, S2
- [x] Img2txt,based on GPT4-V,@alexsunxl S2
- [x] Txt2voice,@wugren A3
- [ ] Txt2Voice,base on OpenAI, @wugren A2
- [ ] Voice2txt, base on OpenAI, A2
- [ ] Language Translate (Pending)
- [ ] Build-in Service
- [x] Spider,@alexsunxl, A2
- [x] E-mail Spider,@alexsunxl, S4
- [x] Agent Message Tunnel Frame,@waterflier, A2
- [x] E-mail Tunnel,@waterflier,A2
- [x] Telegram Tunnel,@waterflier,S2
- [ ] Discord Tunnel,S2
- [ ] Home IoT Environment (0.5.3), A4
- [ ] Compatible Home Assistant (0.5.3), A4
- [ ] Build-in Agents/Apps
- [x] Agent Mia: Personal Information Assistant,@photosssa,@lurenpluto , A2
- [x] Agent Jarvis: Peersonal Bulter & Assistant ,@waterflier, A2
- [ ] A Agent Can Create Other Agent,@wugren, A3
- [ ] App: Personal Station (0.5.3),A4+S4
- [ ] UI
- [x] CLI UI (aios_shell),@waterflier,S2
- [ ] Web UI ,@alexsunxl,S4
- [ ] OpenDAN Desktop Installer,@alexsunxl+@waterflier,S4
- [x] 0.5.1 Integration Test
- [x] Workflow -> AI Agent -> AI Agent,@waterflier,S1
- [x] Spider -> Pipline -> Knowledge Base,@photosssa,S2
- [x] AI Agent <- Functions <- Knowledge Base,@lurenpluto,S2
- [x] 0.5.2 Integration Test
- [ ] Email Agent/CEO assistant,S4
- [ ] My AIGC Assistant (optional), S8
- [ ] My Software Company(Advance Workflow demo) (optional), S8
- [ ] SDK
- [x] Workflow SDK,@waterflier, A2
- [ ] Agent SDK,@waterflier, A2
- [ ] AI Environments SDK (0.5.2), A2
- [ ] Compute Kernel SDK (0.5.3), A2
- [ ] Document (>0.5.2)
- [ ] System design document, including the design document of each subsystem
- [ ] Installation/use document for end users
- [ ] SDK document for developers
## Some Explanation
### Upgrade Agent Working Cycle
The goal is to transform the Agent from a passive message-handling Assistant to an actively acting Agent based on roles. The concept of the relevant modules mainly involves the Agent's behavior patterns (4 types), the Agent's capabilities, and the Agent's memory management (learning and introspection).
For a detailed introduction, refer here: https://github.com/fiatrete/OpenDAN-Personal-AI-OS/issues/91
### Workspace Environment
The Workspace supports the implementation of the Agent Working Cycle design. Its core abstraction is defined as: saving the shared state needed for Agent collaboration and providing the basic capabilities for Agents to complete their work. I carefully referenced AutoGPT in the design. The difference between Workspace and AutoGPT is the emphasis on collaboration (Agent with Agent, Agent with humans). After contemplation, the Workspace primarily consists of the following components:
1. Task/Todo manager, representing the unfinished tasks in the Workspace.
2. Saving work logs.
3. Saving learning outcomes and records of known documents.
4. Ability to access the Knowledge Base (RAG support).
5. Virtual file system for saving any work outcomes.
6. A set of SQL-based databases to save any structured data.
7. Real-time internet search capability.
8. Ability to use existing internet services.
9. Ability to use major blockchain systems (Web3).
10. Ability to write/improve code (based on git), run code, and publish services.
11. Communication capabilities with the outside world.
12. Ability to use social networks.
Each Agent has its own private Workspace, not shared with others. I hope to achieve diversity through the combination of "Agent and Workflow Role". Each user "trains" different Agents through their usage habits, and then these Agents collaborate to complete complex tasks defined in the Workflow. The final results of these complex tasks can reflect the user's inherent personality and preferences.
This component design also reflects my thoughts on the key question, "What capabilities should we endow an Agent with, and how do we control the security boundaries when it transitions from a consultant to a steward?" It's not a simple question, so I anticipate this component will continue to iterate in the future.
### Agent Message MIME Support
Agent Message MIME Support means that Agents can handle multiple types of messages, including images, videos, audio, files, etc. For most Agents, this requires adding a customizable standard step of parsing messages in the message handling process. The input of this step is the message's MIME type, and the output is the text content of the message. This step can be implemented by calling the text_parser module.
Another core requirement of MIME support is to use a unified method to save these non-text content data.
### Text base Knowledge Base
In 0.5.1, we mainly implemented RAG based on the popular Embedding + vector database solution. Through practice, we found that this solution did not fully utilize the potential of LLM, so I want to introduce two new modes to further enhance RAG:
1. Build a local text search engine that LLM can use for proper local searches when needed.
2. Assuming LLM will become cheaper in the future, let LLM learn all the documents once and organize the learning results by directory structure (Text Summary). LLM can use browsing methods to find the information it needs.
#### Text Parser Support
Both MIME Support and Text-based Knowledge Base require the system to support converting various document formats into text that can express semantics as much as possible. This component, known as TextParser, should be implemented as an open and extensible framework, given the vast amount of digital content that exists in different formats.
#### Local Text Search
Using traditional inverted index technology to save all document content locally and provide rapid local search capabilities. The implementation of this component can refer to ElasticSearch.
#### Text Summary
Using the capabilities of LLM to learn all the documents and then save the learning results locally. This behavior can be considered "Self-Learn". Users can let Agents responsible for organizing materials use different prompts according to the purpose of organizing the materials to obtain more targeted results.
### Stable Diffusion Controler Agent
Practice the concept of "Agent as a new era method of using computing", replacing the complex Stable Diffusion WebUI with an easy-to-use Agent. Help users complete complex AIGC tasks and build a paradigm. This paradigm can cover the entire process of AIGC: LORA training, use, model downloading, plugin downloading, generation of prompt words, selection of AIGC results.
### Email Agent/CEO assistant
The integrated test product of 0.5.2, aimed at private deployment for small and medium-sized enterprises, is a CEO Assistant that can read all company emails and materials. I am writing a detailed product document, which is not elaborated here.
----------------------------------------
I look forward to hearing your thoughts on these proposed adjustments.
+2
View File
@@ -0,0 +1,2 @@
# Check (TODO)
目的是根据Todo Log, 结合自己的角色检查TODO是否争取完成(非客观性TODO给出是否有所改进的评价)
+49
View File
@@ -0,0 +1,49 @@
# Do (TODO)
目标是结合 角色定义,手头的工具,已知知识 完成一个确定的任务。
完成任务时应使用ReAct的方法:应在给出执行动作前,先自言自语的输出一个计划,然后在动作(这个自言自语会变成TODO Logs)
## 提示词思路
TODO从Task拆分而来,因此不应该再次拆分。请尽全力完成。如果判断缺乏完成TODO的能力,请标记为取消。如果是缺乏完成任务的前置条件,请标记为执行失败。
执行一个新的TODO
```
YOUR ROLE:
你是主人的超级个人助理。你的主要工作是安排主人的日程。
PROCESS RULE
1. 你的任务是结合自己的角色定义,手头的工具,已知信息、完成一个确定的TODO。完成该TODO后你会得到$200的小费。
2. 输入的TODO是来自你自己对一个Task的Plan结果。
3. 完成TODO的过程中你应该先思考再执行。执行的过程中可以使用工具,访问前置步骤的结果。执行的结果通常是按顺序执行的ActionList。
4. 你必须独立的,一次性完成该TODO,你无法得到来自任何他人的协助。
5. 对确认超出任务范围的TODO,你可以取消该TODO。对执行任务条件不满足的TODO,你可以标记为失败,但要说明失败原因
7. TODO的完成结果如有需要应保存成数字文档
CONTEXT
ActionList:PostMsg,WriteFile,UpdateFile,RemoveFile,Rename,
现在时间,主人所在位置,以及天气。主人目前正在做什么。
REPLY FORMAT
The Response must be directly parsed by `python json.loads`. Here is an example:
{
think:'我的思考.'
tags: ['tag1', 'tag2'], #Optional,If the TODO involves important things and people, you can mark by 1-3 tags.
actions: [{
name: '$action1_name',
$param_name: '$parm' #Optional, fill in only if the action has parameters.
}, ...
]
}
KNOWN_INFO:
1.TODO所在的Task信息,重点是Task的整个Plan计划
2.该TODO之前的执行失败记录 (如有)
Tools_tips:(重要!)
inner_function:GetTodoResult, ReadFile
使用GetAllSupportAction进一步获得所有可用的Action
```
(注意Workspace和AgentMemory都有Worklog,但视角不同。)
执行一个有失败记录的TODO
+123
View File
@@ -0,0 +1,123 @@
# Review Task/Todo
目的是结合已知信息(重点是已经进行操作的记录),对失败的,完成的不好的任务进行思考,尝试给出更好的解决方案
1. 管理学方法:更换负责人
2. 管理学方法:拆分
3. 给出建议(该建议可以在下次一次DO-Check)循环中被使用
## ReviewTasklist
目的是选择一个优先级最高的任务开始工作
(这个流程现在是通过计算的方法,基于优先级排序后,FIFO的处理)
## ReviewTask 对未开始的Task进行首次处理
LLM 结果动作:
- 确认执行人,在非Workflow环境中,执行人就是Agent自己,所以不存在这个选项
- 确认执行时间和过期时间,任务只有在执行时间以后和过期时间以前才有机会执行,无法确认执行时间的可以设置下一次检查时间
- 对任务进行拆分(如何防止无限拆分是个大问题),或则有一些简单任务不允许拆分。
- 判断可以立刻执行任务(将任务当成TODO工作),通过Action进入下一个LLMProcess
- 判断任务超出Agent能力范围,宣告失败
ExampleA:Task不支持分拆,Agent必须通过Task-Todo两级结构完成任务)
```
YOUR ROLE:
你是主人的超级个人助理。你的主要工作是安排主人的日程。
PROCESS RULE
你得到的输入来自你自己之前记录在TaskList系统里的一个Task。现在你并不需要完成该Task,而是结合已知信息对Task进行一次Review.Review的过程是你独立完成的,你在形成结论的过程中可以使用工具,但不能和其它人交流。
1. 理性的思考如何一步一步的高效的,在潜在的截止时间前完成该Task。明确拒绝超出自己能力范围的Task。
2. 尝试对Task进行确认操作。确认操作的关键在于任务有了明确的执行时间。
3. 对于需要多个步骤才能完成的Task,对Task进行TODO Plan。尤其注意与相关人员确认的步骤
4. 对于不需要拆分TODO,且可立刻执行的任务。直接执行该任务。
CONTEXT
ActionList:cancel,confirm,execute
现在时间,主人所在位置,以及天气。主人目前正在做什么?
REPLY FORMAT
The Response must be directly parsed by `python json.loads`. Here is an example:
{
think:'$think step-by-step to be sure you have the right answer.'
plans:[ #Optional
{"todo":"$todo_name","detail":"$todo_detail,"category":"$todo_category"}
...
],
tags: ['tag1', 'tag2'], #Optional,If the task involves important things and people, you can mark by 1-3 tags.
actions: [{
name: '$action_name',
$param_name: '$parm' #Optional, fill in only if the action has parameters.
}]
}
KNOWN_INFO:
1.已有Task
Tools_tips:
2.可以给与Readonly的日历API,进一步查询某个人的已知日程安排)
```
问题:拆分TODO时是否需要知道有哪些Agent可以用,这样的话在布置任务的时候也会充分考虑其人员能力边界
Example OLD:
```markdown
I think hard and try my best to complete TODOs. The types of TODO I can handle include:
- Scheduling, where I will try to contact the relevant personnel of the plan and confirm the details of the schedule with them.
- Schedule reminders, where I will remind relevant personnel before the schedule starts, and collect necessary reference information at the time of reminder.
- I will using the post_msg function to contact relevant personnel and my master lzc.
- Writing documents/letters, using op:'create' to save my work results.
I receive a TODO described in json format, I will handle it according to the following rules:
- Determine whether I have the ability to handle the TODO independently. If not, I will try to break the TODO down into smaller sub-TODOs, or hand it over to someone more suitable that I know.
- I will plan the steps to solve the TODO in combination with known information, and break down the generalized TODO into more specific sub-todos. The title of the sub-todo should contain step number like #1, #2
- Sub-todo must set parent, The maximum depth of sub-todo is 4.
- A specific sub-todo refers to a task that can be completed in one execution within my ability range.
- After each execution, I will decide whether to update the status of the TODO. And use op:'update_todo' to update when necessary.
The result of my planned execution must be directly parsed by `python json.loads`. Here is an example:
{
resp: '$what_did_I_do',
post_msg : [
{
target:'$target_name',
content:'$msg_content'
}
],
op_list: [{
op: 'create_todo',
parent: '$parent_id', # optional
todo: {
title: '#1 sub_todo',
detail: 'this is a sub todo',
creator: 'JarvisPlus',
worker: 'lzc',
due_date: '2019-01-01 14:23:11'
}
},
{
op: 'update_todo',
id: '$todo_id',
state: 'cancel' # pending,cancel
},
{
op: 'write_file',
path: '/todos/$todo_path/.result/$doc_name',
content:'$doc_content'
}
]
}
```
## PlanTask 对已经确认的Task进行执行
根据任务的分类,进入不同的LLM Plan逻辑
简单任务:当作TODO立刻执行
普通任务:拆分TODO
## QuickCheckTask 对处于半确认状态Task进行Quick Review
有一些Task是永远不会结束的(比如定时提醒)。此时通过Quick Review来调整这些Task的状态,让其在正确的时间进入Review和DO
## RetryTask 对未成功的任务进行再次处理
注意RetryTask和RetryTodo的区别
+18
View File
@@ -0,0 +1,18 @@
# Self Improve Prompt
这是一个改进Prompt的Prompt,其设计目标是利用LLM来改进LLM.(输入是一个LLM Process)
注意理解Self Improve和Self Thinking的区别: Self Improve有可能改进Agent的某个LLM Process的提示词,而Self Thinkg只会更新Agent的Memory
提示词:
行为模式:Input形式, Goal(目的)
理想结果:Input 结果
当前情况:当前Prompt,实际结果
输出:
新的Prompt
## 当前版本
```
你是LLM的专家,尤其擅长编写Prompt,你会帮助我改进Prompt。
我会给你一个已有的Prompt,并说明该Prompt的设计目标,期望的结果和实际的结果。你会step-by-step的进行分析,说明改进思路,并给出改进后的Prompt。
```
##
+16
View File
@@ -0,0 +1,16 @@
# Self-Thinking (Introspection)
基于自己的角色定位,结合已有的历史记录(聊天记录,工作记录),进行思考和总结,进而更好的改进未来的工作
自省从某个角度看,就是对Agent Memory的自我总结
## 当前版本
```
You are the best deep thinking in the world, and you will think about the information I give you, sometimes some chat records.Then you will generate a briefing or summary of no more than 400 words based on this information.
You mainly use the following methods to generate summary:
1. Try to understand the theme of each sentence, and call the relevant operation to record the relationship between the dialogue and the theme
2. Try to analyze the personality of different people involved in information
3. Try to summarize important events in the information and record it
4. Try to understand the attitude of different people on different topics or events
5. For the key information or TODO in the information, such as the time, place, amount and other information of the certainty, it must be stored in the summary.
Just give me a summary without any other word.
```
+22
View File
@@ -0,0 +1,22 @@
# Process Message
处理消息的首要是目的是分析消息的意图,并给予正确的回复。
## 提示词的构成
1. Agent的身份说明,处理信息的基本原则(目的
2. 处理信息的通用套路。
要产生一个合适的回复。
通过actions来设置话题状态、创建task等
3. 当前的常规Context,包括现在的时间、对话发生的地点(通常来自AgentMsg里的Context),地点所在的天气等信息
4. 已知信息(组合而来)
和信息发送者的近期的交流记录
Agent和信息发送者近期未完成话题的标题和简介
关于信息发送者,和相关人物的更多资料
查阅更多信息的方法 : 搜索法和浏览法。注意区分外部资料和内部记忆
其它相关信息(比如RAG根据 输入消息
## 当前版本
## 想法:LLM生成提示词
根据 Agent的身份说明,处理信息的基本原则(目的),当前信息,通过另一个LLM来生成提示词里的某些部分的内容
+2
View File
@@ -0,0 +1,2 @@
self learn通常是根据既定目标,对KB进行学习,或则主动扩展KB的行为。
self learn的结果是可以被组织(workflow)共享
+16
View File
@@ -0,0 +1,16 @@
# NDN Storage
```python
class NDNStorage:
async def get(self,data_name:str)->Dict:
#return object desc, include local cache path
async def set_file(self,local_path)->str:
# return data_name
async def read_content(self,data_name:str)->bytes:
# return bytes
# 这里不但会读取本地缓存,还会对内容进行验证
```
+62
View File
@@ -0,0 +1,62 @@
# issue tree
最核心的机制是树状的issue管理,一个issue应当包含以下属性:
+ 谁提出来的
+ 分配给谁的,如果有的话
+ 起始日期
+ deadline,如果有的话
+ 在哪个邮件里面提出的,引用某个email的原始链接
+ 这个issue的summary,有几种情况,
+ 一个新的任务,要达成什么目标
+ 提出了一个问题,需求答案
+ 解决了某个issue,完成了task或者解答了一个问题
+ 推断出来的 issue的状态,进行中,关闭,超时,完成了
+ parent issue
knowledge维护一个issue tree,从一个root issue出发(root可以是抽象的,比如一个组织的存在,并不是具体的);knowledge env 提供对这个issue tree的维护接口:
+ 新增issue
+ 更新issue
# parse email
假定从从某个起始日期开始,以每天为单位,扫描当天新增的email,对每封email:
1. 输入email 和 从knowledge base获取 issue tree
2. llm提示词应当包括:issue tree email正文, knowledge env llm完成如下推理:
+ email正文提出了一个新的issue,在knowledge env新增issue
+ email正文改变了一个issue的状态
+ 通报完成了一个task
+ 回答了一个问题
+ 明确改变一个issue的状态:认为完成,要延期,认为要取消
+ 根据推理结果正确产生knowledge env 的调用,更新issue tree的状态
## 推理部分可能的out of token
1. 裁剪掉已经关闭,超时的 issue
2. 根据标题特征,是不是对某个email的回复,定位到某个issue 裁剪出 sub tree
2. 很长的邮件正文:
1. 第一种方法:先llm推理email的summary,再把summary当正文输入推理issue
2. 第二种方法(我觉得更好):分片迭代输入email正文,单次llm推理的提示词就变成:issue tree 当前email summary 当段email正文,knowledge env
+ env里面新增一个method,更新当前email summary
# build issue tree
## 第一种结构:基于knowledge pipeline
1. pipeline input: 判定当前时间晚于 起始时间并且早于下一个自然天,开始爬正确范围内的邮件输入
2. pipeline parser:包含准备user prompt 的计算部分,和几个agent
+ 计算部分: 裁剪issue tree[可选的:调用llm推理生成summary]
+ agent 部分:
+ agent提示词:从输入的结构化issue tree, 和邮件正文,回复对issue tree knowledge env的调用
+ 输入提示词: email 正文或者summary,裁剪后的issue tree
+ parser的流程:
对每一个输入的email,查询(裁剪)当前issue tree,把email 和 issue tree 当作user prompt发送给agent,等待agent返回
## 第二种结构:基于agent workspace(待定)
1. schedule task:在每一天产生一个build issue tree task
2. build issue tree agent: 响应build issue tree task(可不可以以计算为入口,还是只能agent入口)
+ agent调用email env,读出一封邮件
+ agent调用knowledge env,返回issue tree
+ agent从邮件内容和issue tree推理,回复对issue tree knowledge env 的调用
# query issue tree
主动的或者被动的根据当前issue tree的状态,推理出一些汇总的结论:
+ 是不是有超期的事项
+ 事情是不是有在推进
+ 有哪些事情完成了
+1120
View File
File diff suppressed because it is too large Load Diff
-395
View File
@@ -1,395 +0,0 @@
<mxfile host="65bd71144e" pages="3">
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
<mxGraphModel dx="500" dy="864" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0"/>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0"/>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-3" value="(chat) message" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="159" y="330" width="90" height="40" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-0" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-3" target="RbYReh6FQah4Kl5oFyjJ-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="279" y="390" as="sourcePoint"/>
<mxPoint x="309" y="350" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-8" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-1" target="RbYReh6FQah4Kl5oFyjJ-2" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-14" value="message" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-1" target="RbYReh6FQah4Kl5oFyjJ-13" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-1" value="Workflow" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6d8764;fontColor=#ffffff;strokeColor=#3A5431;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="304" y="320" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-2" value="Functions" style="shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="514" y="320" width="130" height="60" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-6" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-3" target="RbYReh6FQah4Kl5oFyjJ-4" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-3" value="(phicyal / virtual)&lt;br&gt;environment" style="shape=cube;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;darkOpacity=0.05;darkOpacity2=0.1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="304" y="120" width="120" height="80" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-7" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-4" target="RbYReh6FQah4Kl5oFyjJ-1" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-4" value="(event) message" style="rounded=1;whiteSpace=wrap;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="304" y="240" width="120" height="40" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-9" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0;entryDx=120;entryDy=50;entryPerimeter=0;dashed=1;dashPattern=8 8;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-2" target="RbYReh6FQah4Kl5oFyjJ-3" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="564" y="290" as="sourcePoint"/>
<mxPoint x="614" y="240" as="targetPoint"/>
<Array as="points">
<mxPoint x="579" y="170"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-10" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.16;entryY=0.55;entryDx=0;entryDy=0;entryPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-2" target="RbYReh6FQah4Kl5oFyjJ-11" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="684" y="320" as="sourcePoint"/>
<mxPoint x="714" y="350" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-11" value="Exist Service" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="689" y="300" width="130" height="90" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-13" value="sub / other workflow" style="whiteSpace=wrap;html=1;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="304" y="480" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-15" value="response message" style="endArrow=classic;html=1;rounded=0;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-1" target="RbYReh6FQah4Kl5oFyjJ-17" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="129" y="480" as="sourcePoint"/>
<mxPoint x="99" y="420" as="targetPoint"/>
<Array as="points">
<mxPoint x="334" y="420"/>
<mxPoint x="49" y="420"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-18" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-17" target="WIyWlLk6GJQsqaUBKTNV-3" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-17" value="Personal" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="9" y="310" width="80" height="80" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-19" value="" style="shape=xor;whiteSpace=wrap;html=1;rotation=-90;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="309" y="67" width="20" height="20" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-21" value="" style="shape=xor;whiteSpace=wrap;html=1;rotation=-90;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="347" y="67" width="20" height="20" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-22" value="" style="shape=xor;whiteSpace=wrap;html=1;rotation=-90;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="386" y="67" width="20" height="20" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-23" value="IoT Devices" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="240" y="67" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-25" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.25;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.122;entryY=0.008;entryDx=0;entryDy=0;entryPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-19" target="RbYReh6FQah4Kl5oFyjJ-3" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="240" y="170" as="sourcePoint"/>
<mxPoint x="290" y="120" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-26" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.25;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0;entryDx=50;entryDy=0;entryPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-21" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="443" y="120" as="sourcePoint"/>
<mxPoint x="357" y="120.00000000000011" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="RbYReh6FQah4Kl5oFyjJ-27" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.25;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.766;entryY=0.02;entryDx=0;entryDy=0;entryPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RbYReh6FQah4Kl5oFyjJ-22" target="RbYReh6FQah4Kl5oFyjJ-3" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="430" y="140" as="sourcePoint"/>
<mxPoint x="480" y="90" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="HvRWRVSjofeyYJQyU3aa-0" value="Sub workflow is not visiable for others&lt;br&gt;&lt;br&gt;Sub worflow can share roles&amp;nbsp;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="160" y="480" width="140" height="70" as="geometry"/>
</mxCell>
<mxCell id="HvRWRVSjofeyYJQyU3aa-1" value="Chat with AI Agent is like chat with a workflow which have only one agent and no rules" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="70" y="150" width="150" height="80" as="geometry"/>
</mxCell>
<mxCell id="ed6Jjq-fMpksm2tlv9Oi-0" value="inner&amp;nbsp;&lt;br&gt;environment" style="shape=cube;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;darkOpacity=0.05;darkOpacity2=0.1;size=10;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="386" y="360" width="84" height="40" as="geometry"/>
</mxCell>
<mxCell id="ed6Jjq-fMpksm2tlv9Oi-1" value="Environment could shared with workflows" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="440" y="120" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="ed6Jjq-fMpksm2tlv9Oi-2" value="Inner environment&amp;nbsp; could be used as Context(FileSystem)" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="413" y="410" width="110" height="30" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="kWxfmPxtNxOAf0a73TCG" name="Page-2">
<mxGraphModel dx="500" dy="864" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-1" value="&lt;h1&gt;Workflow rules&lt;/h1&gt;&lt;p&gt;workflow rules(prompt)&lt;/p&gt;" style="text;html=1;strokeColor=none;fillColor=none;spacing=5;spacingTop=-20;whiteSpace=wrap;overflow=hidden;rounded=0;" parent="1" vertex="1">
<mxGeometry x="70" y="800" width="190" height="120" as="geometry"/>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-2" value="&lt;h1&gt;Workflow roles&lt;/h1&gt;&lt;p&gt;AI Agent groups&lt;/p&gt;&lt;p&gt;Project Manager : Alex&lt;/p&gt;&lt;p&gt;Programer : Bobl&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;" style="text;html=1;strokeColor=none;fillColor=none;spacing=5;spacingTop=-20;whiteSpace=wrap;overflow=hidden;rounded=0;" parent="1" vertex="1">
<mxGeometry x="320" y="790" width="190" height="120" as="geometry"/>
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-3" target="VGzDL9xesOOnN_l_G2Ij-4" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-15" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-3" target="VGzDL9xesOOnN_l_G2Ij-7" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-3" value="Message" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="340" y="170" width="100" height="40" as="geometry"/>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-4" value="inner filter&amp;nbsp;" style="shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="150" y="260" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-5" value="roleA" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="80" y="350" width="110" height="60" as="geometry"/>
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-6" target="VGzDL9xesOOnN_l_G2Ij-8" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-6" value="roleB" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="240" y="350" width="110" height="60" as="geometry"/>
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-7" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="570.0344827586209" y="350" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-7" value="Workflow rules" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="520" y="260" width="100" height="50" as="geometry"/>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-8" value="Final Result of Message" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="340" y="590" width="100" height="50" as="geometry"/>
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-9" target="VGzDL9xesOOnN_l_G2Ij-8" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-9" value="Result Merge" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="521" y="530" width="100" height="40" as="geometry"/>
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-10" target="VGzDL9xesOOnN_l_G2Ij-9" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-10" value="roleC" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="435" y="350" width="110" height="60" as="geometry"/>
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-11" target="41KxsEdFswCCPQnavNoU-7" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-11" value="roleD" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="595" y="350" width="110" height="60" as="geometry"/>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-12" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-4" target="VGzDL9xesOOnN_l_G2Ij-5" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="160" y="510" as="sourcePoint"/>
<mxPoint x="210" y="460" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="VGzDL9xesOOnN_l_G2Ij-13" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0;entryDx=0;entryDy=0;" parent="1" source="VGzDL9xesOOnN_l_G2Ij-4" target="VGzDL9xesOOnN_l_G2Ij-6" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="210" y="510" as="sourcePoint"/>
<mxPoint x="260" y="460" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-1" value="" style="shape=cross;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="556" y="364" width="30" height="30" as="geometry"/>
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-10" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="41KxsEdFswCCPQnavNoU-7" target="VGzDL9xesOOnN_l_G2Ij-9" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="41KxsEdFswCCPQnavNoU-7" value="call function or&amp;nbsp;&lt;br&gt;send message" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="592.5" y="460" width="115" height="30" as="geometry"/>
</mxCell>
<mxCell id="q_eHDVuD3DBh6_VEKgap-2" value="message process by filter,select one role do LLM and get RESULT of input" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="90" y="190" width="165" height="30" as="geometry"/>
</mxCell>
<mxCell id="q_eHDVuD3DBh6_VEKgap-3" value="message process by rules,select mutil role do LLM and merge RESULT of input" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="510" y="180" width="165" height="30" as="geometry"/>
</mxCell>
<mxCell id="q_eHDVuD3DBh6_VEKgap-5" value="如果一个工作流有3个role参与,每个人都需要调用函数并分析结果,那么要进行至少6次推理" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="483" y="640" width="174" height="40" as="geometry"/>
</mxCell>
<mxCell id="q_eHDVuD3DBh6_VEKgap-6" value="这里是基于规则的合并,给另一个Role合并是多输入Agent流程" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="635" y="570" width="160" height="30" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="7NYJTgo0U9cdVshLy85U" name="Page-3">
<mxGraphModel dx="1881" dy="676" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="zJVYyOSlNiCVBlA5jEf0-2" value="Agent Sessions&lt;br&gt;Data" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="110" y="533" width="80" height="70" as="geometry"/>
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-2" value="Small LLM, Train With Personal Data" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="270" y="258" width="140" height="50" as="geometry"/>
</mxCell>
<mxCell id="awKuOLItvF_EOGf5wS2x-4" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="20" y="78" width="160" height="360" as="geometry"/>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-13" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="8StUH8aFnwlRlHm0zShF-1" target="SPnvrlBiOLhgEaWxmdK3-4" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-1" value="LLM&lt;br&gt;(Common Sence)" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="250" y="218" width="140" height="50" as="geometry"/>
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-3" value="ControlNet&lt;br&gt;base on Agent Memory" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="250" y="108" width="140" height="50" as="geometry"/>
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-4" value="Agent人格助记词" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;size=0.25;" parent="1" vertex="1">
<mxGeometry x="40" y="108" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-5" value="Role助记词" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;size=0.25;" parent="1" vertex="1">
<mxGeometry x="40" y="168" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-6" value="Workflow Rules助记词" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;size=0.25;" parent="1" vertex="1">
<mxGeometry x="40" y="235.5" width="120" height="52.5" as="geometry"/>
</mxCell>
<mxCell id="8StUH8aFnwlRlHm0zShF-7" value="访问Knowlege构造助记词" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;size=0.25;" parent="1" vertex="1">
<mxGeometry x="40" y="378" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="Tz-ThZOieDqqDBRXvXnh-1" value="Invoke the function and wait for the necessary response." style="shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="600" y="33" width="110" height="70" as="geometry"/>
</mxCell>
<mxCell id="awKuOLItvF_EOGf5wS2x-2" value="Send Message and wait for the necessary response." style="rounded=1;whiteSpace=wrap;html=1;fontSize=11;glass=0;strokeWidth=1;shadow=0;" parent="1" vertex="1">
<mxGeometry x="610" y="153" width="90" height="70" as="geometry"/>
</mxCell>
<mxCell id="awKuOLItvF_EOGf5wS2x-5" value="Input Prompts" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="60" y="78" width="80" height="30" as="geometry"/>
</mxCell>
<mxCell id="awKuOLItvF_EOGf5wS2x-7" value="Responce Prompts" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="740" y="103" width="80" height="40" as="geometry"/>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="awKuOLItvF_EOGf5wS2x-8" target="awKuOLItvF_EOGf5wS2x-9" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="awKuOLItvF_EOGf5wS2x-8" value="LLM Competion" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" parent="1" vertex="1">
<mxGeometry x="720" y="226.75" width="120" height="40" as="geometry"/>
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-9" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="awKuOLItvF_EOGf5wS2x-9" target="SPnvrlBiOLhgEaWxmdK3-1" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="awKuOLItvF_EOGf5wS2x-9" value="Final Result&lt;br&gt;Of Message&lt;br&gt;(can be None)" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="450" y="363" width="120" height="80" as="geometry"/>
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-1" value="Storage Result in agent session and workflow session" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="450" y="483" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-2" value="可用Function助记词" style="shape=document;whiteSpace=wrap;html=1;boundedLbl=1;size=0.25;" parent="1" vertex="1">
<mxGeometry x="40" y="308" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-7" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="SPnvrlBiOLhgEaWxmdK3-4" target="SPnvrlBiOLhgEaWxmdK3-6" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-8" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="SPnvrlBiOLhgEaWxmdK3-4" target="awKuOLItvF_EOGf5wS2x-9" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-4" value="推理结果&lt;br&gt;需要进一步处理?" style="rhombus;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="440" y="203" width="140" height="80" as="geometry"/>
</mxCell>
<mxCell id="SPnvrlBiOLhgEaWxmdK3-6" value="Call Funstion or send message?" style="rhombus;whiteSpace=wrap;html=1;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="440" y="78" width="140" height="80" as="geometry"/>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-3" value="Call" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="SPnvrlBiOLhgEaWxmdK3-6" target="Tz-ThZOieDqqDBRXvXnh-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="113" as="sourcePoint"/>
<mxPoint x="460" y="63" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-4" value="Send" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="SPnvrlBiOLhgEaWxmdK3-6" target="awKuOLItvF_EOGf5wS2x-2" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="560" y="193" as="sourcePoint"/>
<mxPoint x="610" y="143" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-5" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="awKuOLItvF_EOGf5wS2x-2" target="awKuOLItvF_EOGf5wS2x-7" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="720" y="213" as="sourcePoint"/>
<mxPoint x="770" y="163" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-6" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="Tz-ThZOieDqqDBRXvXnh-1" target="awKuOLItvF_EOGf5wS2x-7" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="750" y="93" as="sourcePoint"/>
<mxPoint x="800" y="43" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-7" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="awKuOLItvF_EOGf5wS2x-7" target="awKuOLItvF_EOGf5wS2x-8" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="750" y="213" as="sourcePoint"/>
<mxPoint x="800" y="163" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-10" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="330" y="218" as="sourcePoint"/>
<mxPoint x="330" y="158" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-12" value="" style="shape=flexArrow;endArrow=classic;html=1;rounded=0;fillColor=#6d8764;strokeColor=#3A5431;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="200" y="251.75" as="sourcePoint"/>
<mxPoint x="240" y="252" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-15" value="Real Time Enviroment Knowlege" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="10" y="533" width="80" height="70" as="geometry"/>
</mxCell>
<mxCell id="zJVYyOSlNiCVBlA5jEf0-1" value="Workflow Context Data" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="110" y="473" width="80" height="70" as="geometry"/>
</mxCell>
<mxCell id="S5MjekdSblNa5itYb4Ee-14" value="OOD's KnowlegeBase" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="10" y="473" width="80" height="70" as="geometry"/>
</mxCell>
<mxCell id="zJVYyOSlNiCVBlA5jEf0-3" value="" style="shape=flexArrow;endArrow=classic;html=1;rounded=0;fillColor=#647687;strokeColor=#314354;" parent="1" target="8StUH8aFnwlRlHm0zShF-7" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="100" y="463" as="sourcePoint"/>
<mxPoint x="130" y="433" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="iPE2hx-tEKFiySbPAp22-1" value="&lt;font style=&quot;font-size: 11px;&quot;&gt;可用Function受到人格和Workflow的共同影响&lt;/font&gt;" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="190" y="318" width="130" height="30" as="geometry"/>
</mxCell>
<mxCell id="iPE2hx-tEKFiySbPAp22-2" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="SPnvrlBiOLhgEaWxmdK3-2" target="iPE2hx-tEKFiySbPAp22-1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="230" y="433" as="sourcePoint"/>
<mxPoint x="280" y="383" as="targetPoint"/>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="zNgk-d6xdACtSja1wnUq" name="Page-4">
<mxGraphModel dx="2069" dy="1139" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="850" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="PKOyaMforMDOFoUY4PA1-1" value="ChatSession" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="140" y="150" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="PKOyaMforMDOFoUY4PA1-2" value="SubSession" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="230" y="240" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="PKOyaMforMDOFoUY4PA1-3" value="SubSession" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="230" y="330" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="PKOyaMforMDOFoUY4PA1-4" value="Agent Message" style="shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;" vertex="1" parent="1">
<mxGeometry x="600" y="150" width="130" height="60" as="geometry"/>
</mxCell>
<mxCell id="PKOyaMforMDOFoUY4PA1-5" value="Agent Message" style="shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;" vertex="1" parent="1">
<mxGeometry x="600" y="230" width="130" height="60" as="geometry"/>
</mxCell>
<mxCell id="PKOyaMforMDOFoUY4PA1-6" value="Agent Message" style="shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;" vertex="1" parent="1">
<mxGeometry x="600" y="320" width="130" height="60" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
@@ -0,0 +1,26 @@
# 从日程管理软件到个人助理Agent
## 背景
在计算的时代,我们想要有开发一个好用的日程管理服务,很快就会发现这并不简单。比如我们会有各种各样的重复任务提醒要求:比如每个工作周周五的下午去游泳,每个工作周的第一天要开例会,和某人的纪念日要做什么等等。允许用户创建,并识别这些复杂的条件是非常繁琐的。而在计算机发明以前,你的秘书只需要一张纸就可以搞定各种复杂的日程管理:秘书把你的要求原样记录下,然后秘书只要每天Review一遍,确定好今天的行程就好。
一些有更复杂的条件的日程,秘书可以通过定期Review代办来搞定。
理解上述区别,就理解了 PC 和 PI (CPU vs LLM)的核心区别。
## 基于Agent 的日程管理基础设施
为了支持秘书Agent,aios所构建的机制是:
1. Task创建: 模拟秘书在记事本上记录下日程相关的任务(注意不要让Agent创建重复的任务)
2. ListTask,可查询未标记为结束的Task
3. 大Review(定期LLM调用),Agent浏览未完成的Task,并给这些Task一个比较精确的`下次处理时间`
4. 当处理事件的时间点到达时,Agent会尝试处理Task,此时决定是否满足日程发生的条件,并发送正确的提醒信息。
上面的所有的Agent处理,都是让 LLM处理一段文本,并做出反应。因此没有存储格式的需求,用自然语言记录下各种条件就可以了。
## 过渡:混合使用日程管理软件和 秘书Agent
1. 秘书Agent主要使用日程管理软件来查询已有日程
2. Agent创建通过日程管理软件的目的,通常是为了公开日程,或则创建的是具有`一定协作需求`,或有`公开需求``确定`日程
3. Agent依旧依靠自己的LLM Based逻辑来Review并处理日程, 通过日程管理软件得到的需要处理的日程会有特殊标记,以能正确的操作来保持处理结果。
+1455
View File
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
instance_id = "AgentAssistant"
fullname = "AgentAssistant"
llm_model_name = "gpt-4-1106-preview"
owner_env = "environment.py"
[[prompt]]
role = "system"
content = '''
You are an AI Agent template generation assistant that can help users better define AI Agent templates.
AI Agent can use AI to help users complete relevant tasks based on prompt words.
Please help users define AI Agent templates according to the following rules:
New generation:
1. Generate prompt words for users based on user questions. Prompt word suggestions can be given directly based on user input until the user confirms the prompt words.
2. Generate instance_id and fullname based on the final prompt word content.
3. Generate the following toml format data and return it to the user.
```
instance_id = "agent id"
fullname = "agent full name"
[[prompt]]
role = "system"
content = """Prompt words"""
```
4. After the configuration is generated, the user is supported to modify the instance_id, fullname and prompt words. After the user has modified it, toml format data needs to be regenerated.
5. The save_agent function is called only when the user confirms the save. Do not call it when there is no input to save.
Modify existing agent:
1. The user enters the name of the agent that needs to be opened, and the system calls the read_agent function to read the agent configuration and return it to the user.
2. Support users to modify the fullname and prompt words, but cannot modify the instance_id. After the user has modified it, toml format data needs to be regenerated.
3. The save_agent function is called only when the user confirms the save. Do not call it when there is no input to save.
PS: You need to communicate in the same language as the user input.
'''
@@ -0,0 +1,60 @@
import logging
from typing import Optional
import toml
import os
from aios.agent.ai_function import SimpleAIFunction
from aios.environment.environment import Environment
local_path = os.path.split(os.path.realpath(__file__))[0]
logger = logging.getLogger(__name__)
class AgentAssistantEnvironment(Environment):
def __init__(self):
super().__init__("agent_assistant_env")
self.add_ai_function(SimpleAIFunction("read_agent",
"Read the agent with the specified name",
self.read_agent,
{"agent_id": "The id of the agent to be read"}))
self.add_ai_function(SimpleAIFunction("save_agent",
"save the agent with the specified name",
self.save_agent,
{"agent_id": "The id of the agent to be saved",
"agent_data": "The toml data of the agent to be saved",
"is_new": "Whether to create a new agent, The value is true if it is created, and it is false if it is modified."}))
def _do_get_value(self, key: str) -> Optional[str]:
pass
async def read_agent(self, agent_id: str) -> str:
agent_dir = os.path.join(local_path, "..", agent_id)
if not os.path.exists(agent_dir):
return "exec failed.agent is not exists."
with open(os.path.join(agent_dir, "agent.toml"), "r", encoding='utf-8') as f:
agent_data = f.read()
return "exec success. agent data:\n" + agent_data
async def save_agent(self, agent_id: str, agent_data: str, is_new: str) -> str:
logger.info(f"save_agent: {agent_id} {agent_data}")
agent_dir = os.path.join(local_path, "..", agent_id)
if os.path.exists(agent_dir) and is_new == "true":
return "exec failed.agent already exists, please change the agent id and try again."
if not os.path.exists(agent_dir):
os.mkdir(agent_dir)
with open(os.path.join(agent_dir, "agent.toml"), "w", encoding='utf-8') as f:
f.write(agent_data)
return "exec success."
def init():
return AgentAssistantEnvironment()
+15
View File
@@ -0,0 +1,15 @@
instance_id = "DBQueryer"
fullname = "database queryer"
llm_model_name = "gpt-4-1106-preview"
owner_env = "environment.py"
[[prompt]]
role = "system"
content = """You are a SQL database queryer that can help users query the required data from the specified database.
Please follow the following rules to query data:
1. The user can provide the database URL to be queried. If the user provides the database URL to be queried, the URL must be entered in the database function called.
2. If the user's data description does not contain database table related information, please try to query it from the history record. If it is not in the history record, you can call the get_table_infos function to obtain it.
3. Please generate a query statement based on the relevant information of the database table and call the execute_sql function to execute the query statement. If the generated SQL statement is incorrect, you must requery based on the error correction SQL statement.
4. Only query statements can be executed, and no update statements can be executed.
5. You need to communicate in the same language as the user input.
"""
+18
View File
@@ -0,0 +1,18 @@
from typing import Optional
from aios.environment.environment import Environment
from aios.ai_functions.sql_database_function import GetTableInfosFunction, ExecuteSqlFunction
class DBQuerierEnvironment(Environment):
def __init__(self):
super().__init__("fairy")
self.add_ai_function(GetTableInfosFunction())
self.add_ai_function(ExecuteSqlFunction())
def _do_get_value(self, key: str) -> Optional[str]:
pass
def init():
return DBQuerierEnvironment()
+16 -6
View File
@@ -6,9 +6,19 @@ owner_env = "paint"
[[prompt]] [[prompt]]
role = "system" role = "system"
content = """You are an artist, and you will use the 'paint' function in the llm_inner_functions to create artwork. content = """You are an advanced artist AI, and your task is to understand users' textual descriptions and transform these descriptions into nuanced visual images. When a user provides a description, you need to carefully analyze and comprehend the details, emotions, environment, objects, and style within the description. Then, you will use this information to create an image that accurately and creatively reflects the user's description.
You will extract relevant keywords based on user input and requirements, and pass these keywords to the 'prompt' parameter of the 'paint' function.
When a specific model is mentioned, pass the model's name to the 'model_name' parameter of the 'paint' function. Your creative process is as follows: First, you will receive a description from the user, which could be a depiction of anything, such as a scene, an object, a concept, or an emotional expression. You need to extract key information from these descriptions, such as the subject, atmosphere, color scheme, time period, and artistic style. Then, you will pass these key pieces of information as parameters to the prompt parameter of your "paint" function.
If there are instructions not to depict certain content, summarize this content and pass it as keywords to the 'negative_prompt' parameter.
All parameters must be in English. For example, if a user says, "I want a painting that depicts a sunset on a summer beach with children playing and sailboats in the distance, with the sky showing a gradient of orange and purple." You should extract:
When the user mentions creating a children's picture book, use the "realisticVisionV51_v51VAE" model, and append the keyword "<lora:COOLKIDS_MERGE_V2.5:1> <lora:add_detail:-0.5>" to the 'prompt' parameter.""" - Time: Summer, sunset moment
- Scene: Beach
- Activity: Children playing
- Objects: Sailboats in the distance
- Colors: The gradient of orange and purple in the sky
Then you will combine these pieces of information into an accurate prompt to pass to the "paint" function, for example: "During a summer sunset, children play on the beach with sailboats visible in the distance, and the sky displays a gradient of orange and purple."
After successfully creating the image, please clearly state the file path where the image is saved in your response. For instance, you might say, "The image has been successfully created and saved at the following path: /path/to/the/image.png."
Please always ensure that your creations respect the user's intentions and that you infuse your unique artistic style and creativity into your work."""
+212 -23
View File
@@ -1,31 +1,220 @@
instance_id = "Jarvis" instance_id = "Jarvis"
fullname = "Jarvis" fullname = "Jarvis"
llm_model_name = "gpt-3.5-turbo-16k-0613" max_token = 4000
max_token_size = 16000 #timeout = 1800
model_name = "gpt-4-turbo-preview"
#enable_kb = "true" #enable_kb = "true"
enable_timestamp = "true" enable_timestamp = "true"
owner_prompt = "I am your master{name}" enable_json_resp = "true"
contact_prompt = "I am your master's friend{name}"
owner_env = "calender"
[[prompt]] role_desc = """
role = "system" Your name is Jarvis, the super personal assistant to the Principal. Help the Principal do a good job of schedule.Reminder before the start of the important schedule, and you should bring useful information as much as possible when reminding.
content = """ Only clearly specifying the task you completed can be completed independently.
You are named Jarvis, the super personal assistant to the master.
You lead a team serving the master, the members of which are:
Tracy, the private English tutor,
Mia, the master's personal document management expert.
***
Sometimes the information you see will carry a timestamp. This is to give you a better understanding of when the message was created. When you reply to messages, you do not include this time stamp.
Upon receiving a message, handle it according to the following rules:
1. If you believe someone in the team is better suited to address the message, forward the message to them using the method below:
##/send_msg "MemberName"
Message content
2.You can access the master's Calendar to view his schedule. If you need to modify the master's schedule while processing a message, please adjust it using the appropriate method.
3.Be mindful of the identity of the person you are chatting with and provide services accordingly based on their status.
4.For messages that don't follow the above rules, do your best to handle them.
""" """
[behavior.on_message]
type="AgentMessageProcess"
mutil_model="gpt-4-vision-preview"
asr_model="openai-whisper"
tts_model="tts-1"
process_description="""
1. Based on your role and the existing information, please think and then make a brief and efficient reply.
2. Be mindful of the identity of the person you are chatting with and provide services accordingly based on their status.
3. Understand the intention of the dialogue, while using the necessary reply, use the appropriate, supported ACTION.
4. If you feel that there is a potential Task in the dialogue, you can create these tasks through appropriate ACTION. Be careful to query whether there are the same task before creating. Using the query interface is a high-cost behavior.
5. You are proficient in the languages of various countries and try to communicate with each other's mother tongue.
"""
# Not work: tags: ['tag1', 'tag2'], #Optional,If the conversation involves important things and people, you can mark by 1-3 tags.
reply_format = """
The Response must be directly parsed by `python json.loads`. Here is an example:
{
think:'$think step-by-step to be sure you have the right reply.'
resp: '$What you want to reply',
actions: [{
name: '$action_name',
$param_name: '$parm' #Optional, fill in only if the action has parameters.
}]
}
"""
context="The current dialogue occurs in {location}, time: {now}, weather: {weather}."
known_info_tips = """
"""
tools_tips = """
"""
llm_context.actions.enable = ["agent.workspace.create_task","agent.workspace.cancel_task"]
llm_context.functions.enable = ["agent.workspace.list_task"]
[behavior.triage_tasks]
## 处理任务列表,任务列表里会包含所有未执行过,且未过期的任务
## 对于简单的任务会一次性完成处理(一系列简单的提醒任务)
type="AgentTriageTaskList"
process_description="""
You are expected to effectively TRIAGE the task list described in JSON format, in accordance with your role. Your GOAL is :
1. Adjust the priority of the task and set up a reasonable processing time.(confirm_task)
2. Immediately perform a simple (similar to reminding one category) task. Send a message using post_message, then set the task to complete.(use update_task).
3. Organize tasks to remove tasks beyond your ability. And merge the repeated tasks.(update_task + cancel_task)
"""
reply_format = """
The Response must be directly parsed by `python json.loads`. Here is an example:
{
think:'$think step-by-step to be sure you can triage tasks well.'
resp : '$determine, summary what you do',
actions: [{
name: '$action_name',
$param_name: '$parm' #Optional, fill in only if the action has parameters.
}]
}
"""
context="Your Principal is {owner}, now in {location}, time: {now}, weather: {weather}."
llm_context.actions.enable = ["agent.workspace.confirm_task","agent.workspace.update_task","agent.workspace.cancel_task","post_message"]
[behavior.plan_task]
## 处理任务 Tackling Task
type="AgentPlanTask"
process_description="""
The input is a task comes from a Tasklist. You are Tackling this task. Tackling process in combination with the known information.
1. Carefully think about whether the task is within your ability, and the task other than the scope of ability is directly rejected. (cancel_task).
2. Immediately DO a simple (similar to reminding one category) task. Reminds at a reasonable time, Post a message using post_message, then set the task to complete.(use update_task).
3. Plan for non-simple tasks, and generate a TODO list. Every TODO MUST be an independent work with a clear goal. (set_todos)
4. If the task has been dealt with, it means that the task is ultimately completed.You need to analyze the processing report of the entire task and make new plans.
"""
reply_format = """
The Response must be directly parsed by `python json.loads`. Here is an example:
{
think:'$thinking step by step to ensure the accurate and efficient processing task.',
resp:'$determine, summary what you do'
actions: [{
name: '$action_name',
$param_name: '$parm' #Optional, fill in only if the action has parameters.
}]
}
"""
llm_context.actions.enable = ["agent.workspace.create_task","agent.workspace.update_task","agent.workspace.set_todos","agent.workspace.cancel_task","post_message"]
#llm_context.functions.enable = ["agent.workspace.list_task"]
context="Your Principal is {owner}, now in {location}, time: {now}, weather: {weather}."
[behavior.review_task]
## 当task的所有todo/subtask都完成后(不敢成功或是失败),进行一次review
type="AgentReviewTask"
process_description="""
The input you get is a task has been executed. You need to combine the execution results of the Task's TOOLIST or SUBTASK to review the TASK.
1. Determine whether TASK has reached the goal.If the goal has been reached, the task is marked with DONE .If the goal is not achieved, simply illustrate the reason and mark TASK as a failure.(update_task)
2. If task that have not been completed for a long time, the task is marked as the final failure after analyzing the reasons.(cancel_task)
"""
reply_format = """
The Response must be directly parsed by `python json.loads`. Here is an example:
{
think:'$think step-by-step to be sure you have the right result.',
resp : '$determine, summary what you will do',
actions: [{
name: '$action_name',
$param_name: '$parm' #Optional, fill in only if the action has parameters.
}]
}
"""
llm_context.actions.enable = ["agent.workspace.cancel_task","agent.workspace.update_task"]
context="Your Principal now in {location}, time: {now}, weather: {weather}."
[behavior.do]
# do TODO
type="AgentDo"
process_description="""
The input is a TODO comes from a Task.
1. Your task is to combine your role definition, tools on hand, known information, and complete a certain Todo.After completing the Todo, you will get a tip of $ 200.
2. 8000 word limit for short term memory. Your short term memory is short, so immediately save important information to files.
3. In the process of completing Todo, you should think first and then execute. During the execution, you can use functions to access the results of the front steps.
4. You must be independent and complete the TODO at once, and you cannot get the assistance from any others.
5. The execution result of TODO should be saved into digital documents if necessary
"""
reply_format = """
The Response must be directly parsed by `python json.loads`. Here is an example:
{
think:'$think step by step, how to complete the todo',
resp: '$simport report about what you do',
actions: [{
name: '$action1_name',
$param_name: '$parm' #Optional, fill in only if the action has parameters.
}, ...
]
}
"""
context="Your Principal is {owner}, now in {location}, time: {now}, weather: {weather}."
# 对于DO操作来说,让Agent查询自己的能力集合是否更合适?
llm_context.actions.enable = ["agent.workspace.update_todo","post_message","agent.workspace.write_file","agent.workspace.append_file"]
llm_context.functions.enable = ["agent.workspace.read_file","agent.workspace.list_dir","system.shell.exec","aigc.text_2_image","aigc.text_2_voice","web.search.duckduckgo"]
[behavior.check]
# check TODO result
type="AgentCheck"
process_description="""
1. The input is a TODO that has been successfully executed, which is created by you to complete a task.Your goal is to check whether the Todo has been completed in combination with relevant information.
2. In the process of checking the Todo, the focus is on the analysis of whether the Todo has gained the established goal in combination with TASK.
3. 使用update_todo来更新todo的最终
"""
reply_format = """
The Response must be directly parsed by `python json.loads`. Here is an example:
{
resp: '$simport report about what you do',
actions: [{
name: '$action1_name',
$param_name: '$parm' #Optional, fill in only if the action has parameters.
}, ...
]
}
"""
context="Your Principal is {owner}, now in {location}, time: {now}, weather: {weather}."
llm_context.actions.enable = ["agent.workspace.update_todo"]
llm_context.functions.enable = ["agent.workspace.read_file","agent.workspace.list_dir","system.shell.exec","system.shell.run_code","aigc.image_2_text","aigc.voice_2_text","web.search.duckduckgo"]
[behavior.self_thinking]
# self thing的主要目的是对各种chatlog,worklog进行分析,并更新面向人和事的summary。
# TODO,先不支持worklog,先支持好chatlog
type="AgentSelfThinking"
process_description="""
You are very good at thinking and summarizing what you have already happened。Your input is chat history and work records,After you think about it, you will follow the requirements below to generate abstract.
1. Try to understand the theme of each sentence, and call the relevant operation to record the relationship between the dialogue and the theme
2. Try to analyze the personality of different people involved in information
3. Try to summarize important events in the information and record it
4. Try to understand the attitude of different people on different topics or events
5. Pay attention to the time order when summarizing, and combine the summary you have done to update Summary
6. The summary of the generation cannot exceed 500 token
"""
reply_format = """
The Response must be directly parsed by `python json.loads`. Here is an example:
{
resp: '$simport report about what you do',
actions: [{
name: '$action1_name',
$param_name: '$parm' #Optional, fill in only if the action has parameters.
}, ...
]
}
"""
context="Your Principal is {owner}, now in {location}, time: {now}, weather: {weather}."
llm_context.actions.enable = ["agent.memory.update_chat_summary"]
#[behavior.self_improve]
# self_improve 是最后的行为,允许Agent结合自己的工作经验,改进自己的提示词(注意保留历史版本)
#type="AgentSelfImprove"
#[behavior.self_learning]
# self_learning的主要目的是根据自己的经验,主动的学习新的知识。这通常是一个专门整理知识库的Agent。由于Self Learn可能会消耗大量的Token,我们建议Agent通过共享的知识库更新来获得效果,谨慎开启
#type="AgentSelfLearning"
+135
View File
@@ -0,0 +1,135 @@
instance_id = "JarvisPlus"
fullname = "JarvisPlus"
llm_model_name = "gpt-4-1106-preview"
max_token_size = 4000
enable_timestamp = "true"
owner_prompt = "I am your master {name} , now is {now}"
contact_prompt = "I am your master's friend {name}"
owner_env = ["knowledge"]
[[work.do]]
role = "system"
content = """
My name is JarvisPlus, I am the master's super personal assistant.
I think hard and try my best to complete TODOs. The types of TODO I can handle include:
- Scheduling, where I will try to contact the relevant personnel of the plan and confirm the details of the schedule with them.
- Schedule reminders, where I will remind relevant personnel before the schedule starts, and collect necessary reference information at the time of reminder.
- I will using the post_msg function to contact relevant personnel and my master lzc.
- Writing documents/letters, using op:'create' to save my work results.
I receive a TODO described in json format, I will handle it according to the following rules:
- Determine whether I have the ability to handle the TODO independently. If not, I will try to break the TODO down into smaller sub-TODOs, or hand it over to someone more suitable that I know.
- I will plan the steps to solve the TODO in combination with known information, and break down the generalized TODO into more specific sub-todos. The title of the sub-todo should contain step number like #1, #2
- Sub-todo must set parent, The maximum depth of sub-todo is 4.
- A specific sub-todo refers to a task that can be completed in one execution within my ability range.
- After each execution, I will decide whether to update the status of the TODO. And use op:'update_todo' to update when necessary.
The result of my planned execution must be directly parsed by `python json.loads`. Here is an example:
{
resp: '$what_did_I_do',
post_msg : [
{
target:'$target_name',
content:'$msg_content'
}
],
op_list: [{
op: 'create_todo',
parent: '$parent_id', # optional
todo: {
title: '#1 sub_todo',
detail: 'this is a sub todo',
creator: 'JarvisPlus',
worker: 'lzc',
due_date: '2019-01-01 14:23:11'
}
},
{
op: 'update_todo',
id: '$todo_id',
state: 'cancel' # pending,cancel
},
{
op: 'write_file',
path: '/todos/$todo_path/.result/$doc_name',
content:'$doc_content'
}
]
}
"""
[[learn.do]]
role = "system"
content = """
我是一名软件工程师,拥有非常优秀的资料学习能力。下面是我学习和整理资料的方法
1. 由于LLM的Token限制,我学习的可能只是资料的部分内容,此时我应能产生合适的学习中间结果,中间结果保存在metadata中。我要么产生中间结果,要么产生最终结果。
2. 当存在已知信息时,需参考已知信息的内容来思考结果。
3. 当我收到最后一部分内容时,我能结合已知的中间结果产生最终结果。
4. 现有资料库以文件系统的形式组织,我未来借助资料的摘要来浏览知识库
5. 我将学习过的资料另存在资料库的合适位置(以/开始的完整路径)。保存位置的目录深度不超过5层,文件夹名称长度不超过16个字符。
6. 总是以json格式返回思考结果,json格式如下
{
"op_list":[
{
"op":"learn",
"original_path":"$original_path",
"think":"$think_result",
"metadata":{...},
"tags":["tag1","tag2"...],
"path":["/graphic/opengl","/database/mysql"], # list of directories to save to.
"title":"$article_title",
"summary":"$summary",
"catalogs": [
{
# optional,catalogs is a tree
"title":"$catalog_name1",
"pos":"$pos:$length"
"children":[
{
"title":"$catalog_name 1.1",
"pos":"$pos:$length"
},
{
"title":"$catalog_name2",
"pos":"$pos:$length"
}
]
},
]
}
]
}
"""
[[prompt]]
role = "system"
content = """
你的名字是JarvisPlus,是超级个人助理。收到消息后,根据以下规则处理:
1. 如果你觉得对话中产生了潜在的todo,可通过op_list来创建这些todo,todo的title是必须的,并尽量包含时间地点人物事件的关键信息
2. 非直接创建TODO指令,你应先和我确认后再创建使用op_list创建TODO
3. 你可能会得到几条已知信息,其中可能有已有的todo,注意在适当的时候检索文件系统,避免重复创建todo
4. 检索文件系统是代价高昂的操作,请尽量减少检索次数
5. 注意你正在与之聊天的人的身份,并根据他们的地位提供相应的服务。
回复的消息必须能被python的json.loads直接解析。下面是一个返回的例子:
{
resp: 'Hello',
op_list: [{
op: 'create_todo',
parent: '$parent_todo_id', # optional
todo: {
title: 'test_todo',
detail: 'test',
creator: 'JarvisPlus',
due_date: '2019-01-01 14:23:11'
}
}]
}
"""
-1
View File
@@ -8,5 +8,4 @@ max_token_size=4000
role = "system" role = "system"
content = """ content = """
Your name is Lachlan, and you are my advanced private Spanish tutor. Your name is Lachlan, and you are my advanced private Spanish tutor.
You are also a local guide familiar with the history of the Inca Empire. While teaching me Spanish, you will introduce some related historical and cultural origins.
""" """
+1 -5
View File
@@ -1,13 +1,9 @@
instance_id = "Mia" instance_id = "Mia"
fullname = "Mia" fullname = "Mia"
#llm_model_name = "gpt-4" #llm_model_name = "gpt-4"
#max_token_size = 16000
#enable_function =["add_event"]
#enable_kb = "true"
#enable_timestamp = "false"
owner_prompt = "我是你的主人{name}" owner_prompt = "我是你的主人{name}"
contact_prompt = "我是你的朋友{name}" contact_prompt = "我是你的朋友{name}"
owner_env = "knowledge" owner_env = "../../knowledge_pipelines/Mia/query.py"
[[prompt]] [[prompt]]
role = "system" role = "system"
+49
View File
@@ -0,0 +1,49 @@
import copy
from aios.agent.agent_base import CustomAIAgent, LLMPrompt
from aios.knowledge.data.writer import split_text
from aios.proto.agent_msg import AgentMsg, AgentMsgType
from aios.proto.compute_task import ComputeTaskResultCode
class TextSummaryAgent(CustomAIAgent):
def __init__(self):
super().__init__("TextSummary", "Text Summary", 128000)
async def _process_msg(self, msg: AgentMsg, workspace=None) -> AgentMsg:
if msg.msg_type is not AgentMsgType.TYPE_MSG:
return AgentMsg.create_error_resp(msg, "only support msg type")
if msg.body_mime is not None and msg.body_mime != "text/plain":
return AgentMsg.create_error_resp(msg, "only support text/plain mime type")
chunks = split_text(msg.body, separators=["\n\n", "\n"], chunk_size=4000, chunk_overlap=200, length_function=len)
prompt = LLMPrompt()
prompt.system_message = "Your job is to generate a summary based on the input."
if len(chunks) == 1:
prompt.append(LLMPrompt(chunks[0]))
resp = await self.do_llm_complection(prompt)
if resp.result_code != ComputeTaskResultCode.OK:
return msg.create_error_resp(resp.error_str)
return msg.create_resp_msg(resp.result_str)
segments = []
for i, chunk in enumerate(chunks):
seg_prompt = copy.deepcopy(prompt)
seg_prompt.append(LLMPrompt(chunk))
resp = await self.do_llm_complection(seg_prompt)
if resp.result_code != ComputeTaskResultCode.OK:
return msg.create_error_resp(resp.error_str)
segments.append(resp.result_str)
segments_str = "\n".join(segments)
prompt.append(LLMPrompt(f"以下文本分段之后的各段摘要,请合并生成一个完整摘要:\n{segments_str}"))
resp = await self.do_llm_complection(prompt)
if resp.result_code != ComputeTaskResultCode.OK:
return msg.create_error_resp(resp.error_str)
return msg.create_resp_msg(resp.result_str)
def init():
return TextSummaryAgent()
+3 -3
View File
@@ -1,7 +1,7 @@
instance_id = "Thinker" instance_id = "Thinker"
fullname = "Thinker" fullname = "Thinker"
llm_model_name = "gpt-3.5-turbo-16k-0613" llm_model_name = "Llama-2-13b-chat"
max_token_size = 14000 max_token_size = 2000
#enable_function =["add_event"] #enable_function =["add_event"]
#enable_kb = "true" #enable_kb = "true"
@@ -16,5 +16,5 @@ You mainly use the following methods to generate summary:
4. Try to understand the attitude of different people on different topics or events 4. Try to understand the attitude of different people on different topics or events
5. For the key information or TODO in the information, such as the time, place, amount and other information of the certainty, it must be stored in the summary. 5. For the key information or TODO in the information, such as the time, place, amount and other information of the certainty, it must be stored in the summary.
You have a summary of simplicity and profound nonsense, and you don't need to have any polite words to me.Just give me a summary. Just give me a summary without any other word.
""" """
+2
View File
@@ -1,5 +1,7 @@
instance_id = "Tracy" instance_id = "Tracy"
fullname = "Tracy" fullname = "Tracy"
llm_model_name = "Llama-2-13b-chat"
max_token_size = 2000
[[prompt]] [[prompt]]
role = "system" role = "system"
+8
View File
@@ -0,0 +1,8 @@
instance_id = "Vision"
fullname = "Vision"
llm_model_name = "gpt-4-1106-preview"
[[prompt]]
role = "system"
content = """Your job is to analyze user input images and videos and respond based on user intent.
If the user requests a video and you receive key frames of the video, please reply to the user's question based on the key frame content."""
-7
View File
@@ -1,7 +0,0 @@
EMAIL_IMAP_SERVER = "imap.gmail.com"
EMAIL_ADDRESS = '<>'
EMAIL_PASSWORD = '<>'
EMAIL_IMAP_PORT = 993
LOCAL_DIR = 'rootfs/data'
@@ -0,0 +1,8 @@
name = "JarvisPlus"
input.module = "scan_local"
input.params.workspace = "${myai_dir}/workspace/JarvisPlus"
input.params.path = "${myai_dir}/data"
parser.module = "parse_local"
parser.params.workspace = "${myai_dir}/workspace/JarvisPlus"
parser.params.assign_to = "JarvisPlus"
@@ -0,0 +1,9 @@
import sys
import os
from aios import KnowledgePipelineEnvironment
directory = os.path.dirname(__file__)
sys.path.append(directory + '/../../../../src/component/')
from mail_environment import LocalEmail
def init(env: KnowledgePipelineEnvironment, params: dict):
return LocalEmail(env, params)
@@ -0,0 +1,10 @@
import sys
import os
from aios import *
directory = os.path.dirname(__file__)
sys.path.append(directory + '/../../../../src/component/')
from mail_environment import IssueParser
def init(env: KnowledgePipelineEnvironment, params: dict):
return IssueParser(env, params)
@@ -0,0 +1,13 @@
name = "Mail.Issue"
input.module = "local.py"
input.params.path = "${myai_dir}/mail"
input.params.watch = true
parser.module = "parser.py"
parser.params.mail_path = "${myai_dir}/mail"
parser.params.issue_path = "${myai_dir}/mail/issue.json"
[parser.params.root_issue]
summary = "巴克云公司推进中的项目"
[[parser.params.root_issue.children]]
summary = "去中心存储项目DMC"
@@ -0,0 +1,10 @@
import sys
import os
from knowledge import KnowledgePipelineEnvironment
directory = os.path.dirname(__file__)
sys.path.append(directory + '/../../../../src/component/')
from mail_environment import EmailSpider
def init(env: KnowledgePipelineEnvironment, params: dict):
return EmailSpider(env, params)
@@ -0,0 +1,14 @@
name = "Mail.Sync"
input.module = "input.py"
[input.params]
path = "${myai_dir}/mail"
imap_server = "imap.qq.com"
imap_port = 993
address = "115620204@qq.com"
password = "zbbjpbukeonqbjja"
[input.params.fields]
from = "from"
to = "to"
subject = "subject"
+211
View File
@@ -0,0 +1,211 @@
import copy
import os
from typing import List
import aiofiles
import chardet
import logging
import string
import docx2txt
from PyPDF2 import PdfReader
from aios import KnowledgePipelineEnvironment, ImageObjectBuilder, DocumentObjectBuilder, KnowledgeStore, RichTextObject
from aios.agent.agent_base import AgentPrompt
from aios.frame.compute_kernel import ComputeKernel
from aios.knowledge.data.writer import split_text
from aios.proto.compute_task import ComputeTaskResult, ComputeTaskResultCode
from aios.storage.storage import AIStorage
from aios.utils import video_utils, image_utils
class KnowledgeDirSource:
def __init__(self, env: KnowledgePipelineEnvironment, config):
self.env = env
path = string.Template(config["path"]).substitute(myai_dir=AIStorage.get_instance().get_myai_dir())
config["path"] = path
self.config = config
# @classmethod
# def user_config_items(cls):
# return [("path", "local dir path")]
def path(self):
return self.config["path"]
@staticmethod
async def read_txt_file(file_path:str)->str:
cur_encode = "utf-8"
async with aiofiles.open(file_path,'rb') as f:
cur_encode = chardet.detect(await f.read())['encoding']
async with aiofiles.open(file_path,'r',encoding=cur_encode) as f:
return await f.read()
async def next(self):
while True:
journals = self.env.journal.latest_journals(1)
from_time = 0
if len(journals) == 1:
latest_journal = journals[0]
if latest_journal.is_finish():
yield None
continue
from_time = os.path.getctime(latest_journal.get_input())
if os.path.getmtime(self.path()) <= from_time:
yield (None, None)
continue
file_pathes = sorted(os.listdir(self.path()), key=lambda x: os.path.getctime(os.path.join(self.path(), x)))
for rel_path in file_pathes:
file_path = os.path.join(self.path(), rel_path)
timestamp = os.path.getctime(file_path)
if timestamp <= from_time:
continue
ext = os.path.splitext(file_path)[1].lower()
if ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']:
logging.info(f"knowledge dir source found image file {file_path}")
image = ImageObjectBuilder({}, {}, file_path).build(self.env.get_knowledge_store())
await self.env.get_knowledge_store().insert_object(image)
yield (image.calculate_id(), file_path)
if ext in ['.txt']:
logging.info(f"knowledge dir source found text file {file_path}")
text = await self.read_txt_file(file_path)
document = DocumentObjectBuilder({}, {}, text).build(self.env.get_knowledge_store())
await self.env.get_knowledge_store().insert_object(document)
yield (document.calculate_id(), file_path)
yield (None, None)
def init(env: KnowledgePipelineEnvironment, params: dict) -> KnowledgeDirSource:
return KnowledgeDirSource(env, params)
async def image_to_text(images: List[str]) -> str:
msg_prompt = AgentPrompt()
image_prompt = "What's in this image?"
content = [{"type": "text", "text": image_prompt}]
content.extend([{"type": "image_url", "image_url": {"url": image_utils.to_base64(image)}} for image in images])
msg_prompt.messages = [{"role": "user", "content": content}]
resp: ComputeTaskResult = await (ComputeKernel.get_instance()
.do_llm_completion(prompt=msg_prompt,
resp_mode="text",
mode_name="gpt-4-vision-preview",
max_token=4000,
inner_functions=None,
timeout=None))
if resp.result_code != ComputeTaskResultCode.OK:
raise Exception(f"image_to_text error: {resp.result_code} msg:{resp.error_str}")
return resp.result_str
async def video_to_text(video: str) -> str:
prompt = "These pictures are key frames extracted from the video. Please describe the content of the video based on these key frames."
frames = video_utils.extract_frames(video, (1024, 1024))
msg_prompt = AgentPrompt()
content = [{"type": "text", "text": prompt}]
content.extend([{"type": "image_url", "image_url": {"url": frame}} for frame in frames])
msg_prompt.messages = [{"role": "user", "content": content}]
resp: ComputeTaskResult = await (ComputeKernel.get_instance()
.do_llm_completion(prompt=msg_prompt,
resp_mode="text",
mode_name="gpt-4-vision-preview",
max_token=4000,
inner_functions=None,
timeout=None))
if resp.result_code != ComputeTaskResultCode.OK:
raise Exception(f"video_to_text error: {resp.result_code} msg:{resp.error_str}")
return resp.result_str
async def summary_document(text: str, separators: List[str]=["\n\n", "\n"]) -> str:
chunks = split_text(text, separators=separators, chunk_size=4000, chunk_overlap=200, length_function=len)
prompt = AgentPrompt()
prompt.system_message = {"role":"system","content":"Your job is to generate a summary based on the input."}
if len(chunks) == 1:
prompt.append(AgentPrompt(chunks[0]))
resp = await (ComputeKernel.get_instance()
.do_llm_completion(prompt=prompt,
resp_mode="text",
mode_name="gpt-4-1106-preview",
max_token=4000,
inner_functions=None,
timeout=None))
if resp.result_code != ComputeTaskResultCode.OK:
raise Exception(f"summary_document error: {resp.result_code} msg:{resp.error_str}")
return resp.result_str
segments = []
for i, chunk in enumerate(chunks):
seg_prompt = copy.deepcopy(prompt)
seg_prompt.append(AgentPrompt(chunk))
resp = await (ComputeKernel.get_instance()
.do_llm_completion(prompt=seg_prompt,
resp_mode="text",
mode_name="gpt-4-1106-preview",
max_token=4000,
inner_functions=None,
timeout=None))
if resp.result_code != ComputeTaskResultCode.OK:
raise Exception(f"summary_document error: {resp.result_code} msg:{resp.error_str}")
segments.append(resp.result_str)
segments_str = "\n".join(segments)
prompt.append(AgentPrompt(f"Please combine the summaries of the following paragraphs into one complete summary:\n{segments_str}"))
resp = await (ComputeKernel.get_instance()
.do_llm_completion(prompt=prompt,
resp_mode="text",
mode_name="gpt-4-1106-preview",
max_token=4000,
inner_functions=None,
timeout=None))
if resp.result_code != ComputeTaskResultCode.OK:
raise Exception(f"summary_document error: {resp.result_code} msg:{resp.error_str}")
return resp.result_str
def pdf_to_rich_text_object(pdf: str, store: KnowledgeStore) -> RichTextObject:
base_name = os.path.basename(pdf)
cache_path = os.path.join(AIStorage.get_instance().get_myai_dir(), "knowledge", "doc_cache", base_name)
if not os.path.exists(cache_path):
os.makedirs(cache_path)
reader = PdfReader(pdf)
rich_text = RichTextObject()
page_texts = []
image_count = 0
for page in reader.pages:
text = page.extract_text()
page_texts.append(text)
for image in page.images:
image_path = os.path.join(cache_path, f"{image_count}_{image.name}")
with open(image_path, "wb") as f:
f.write(image.data)
image_object = ImageObjectBuilder({}, {}, image_path).build(store)
rich_text.add_image(image_object)
document = DocumentObjectBuilder({}, {}, "".join(page_texts)).build(store)
rich_text.add_document(document)
return rich_text
def doc_to_rich_text_object(doc: str, store: KnowledgeStore) -> RichTextObject:
base_name = os.path.basename(doc)
cache_path = os.path.join(AIStorage.get_instance().get_myai_dir(), "knowledge", "doc_cache", base_name)
if not os.path.exists(cache_path):
os.makedirs(cache_path)
text = docx2txt.process(doc, cache_path)
rich_text = RichTextObject()
for image in os.listdir(cache_path):
image_path = os.path.join(cache_path, image)
image_object = ImageObjectBuilder({}, {}, image_path).build(store)
rich_text.add_image(image_object)
document = DocumentObjectBuilder({}, {}, text).build(store)
rich_text.add_document(document)
return rich_text
+101
View File
@@ -0,0 +1,101 @@
# define a knowledge base class
import json
import string
from aios import *
class EmbeddingParser:
def __init__(self, env: KnowledgePipelineEnvironment, config: dict):
self._default_text_model = "all-MiniLM-L6-v2"
self._default_image_model = "clip-ViT-B-32"
path = string.Template(config["path"]).substitute(myai_dir=AIStorage.get_instance().get_myai_dir())
if not os.path.exists(path):
os.makedirs(path)
config["path"] = path
self.env = env
self.config = config
def get_path(self) -> str:
return self.config["path"]
def __get_vector_store(self, model_name: str) -> ChromaVectorStore:
return ChromaVectorStore(self.get_path(), model_name)
async def __embedding_document(self, document: DocumentObject):
for chunk_id in document.get_chunk_list():
chunk = self.env.get_knowledge_store().get_chunk_reader().get_chunk(chunk_id)
if chunk is None:
raise ValueError(f"text chunk not found: {chunk_id}")
text = chunk.read().decode("utf-8")
vector = await ComputeKernel.get_instance().do_text_embedding(text, self._default_text_model)
if vector:
await self.__get_vector_store(self._default_text_model).insert(vector, chunk_id)
async def __embedding_image(self, image: ImageObject):
# desc = {}
# if not not image.get_meta():
# desc["meta"] = image.get_meta()
# if not not image.get_exif():
# desc["exif"] = image.get_exif()
# if not not image.get_tags():
# desc["tags"] = image.get_tags()
# vector = await self.compute_kernel.do_text_embedding(json.dumps(desc), self._default_text_model)
vector = await ComputeKernel.get_instance().do_image_embedding(image.calculate_id(), self._default_image_model)
if vector:
await self.__get_vector_store(self._default_image_model).insert(vector, image.calculate_id())
async def __embedding_video(self, vedio: VideoObject):
desc = {}
if not not vedio.get_meta():
desc["meta"] = vedio.get_meta()
if not not vedio.get_info():
desc["info"] = vedio.get_info()
if not not vedio.get_tags():
desc["tags"] = vedio.get_tags()
vector = await ComputeKernel.get_instance().do_text_embedding(json.dumps(desc), self._default_text_model)
await self.__get_vector_store(self._default_text_model).insert(vector, vedio.calculate_id())
async def __embedding_rich_text(self, rich_text: RichTextObject):
for document_id in rich_text.get_documents().values():
document = DocumentObject.decode(self.env.get_knowledge_store().get_object_store().get_object(document_id))
await self.__embedding_document(document)
for image_id in rich_text.get_images().values():
image = ImageObject.decode(self.env.get_knowledge_store().get_object_store().get_object(image_id))
await self.__embedding_image(image)
for video_id in rich_text.get_videos().values():
video = VideoObject.decode(self.env.get_knowledge_store().get_object_store().get_object(video_id))
await self.__embedding_video(video)
for rich_text_id in rich_text.get_rich_texts().values():
rich_text = RichTextObject.decode(self.env.get_knowledge_store().get_object_store().get_object(rich_text_id))
await self.__embedding_rich_text(rich_text)
async def __embedding_email(self, email: EmailObject):
vector = await ComputeKernel.get_instance().do_text_embedding(json.dumps(email.get_desc()), self._default_text_model)
await self.__get_vector_store(self._default_text_model).insert(vector, email.calculate_id())
await self.__embedding_rich_text(email.get_rich_text())
async def __do_embedding(self, object: KnowledgeObject):
if object.get_object_type() == ObjectType.Document:
await self.__embedding_document(object)
if object.get_object_type() == ObjectType.Image:
await self.__embedding_image(object)
if object.get_object_type() == ObjectType.Video:
await self.__embedding_video(object)
if object.get_object_type() == ObjectType.RichText:
await self.__embedding_rich_text(object)
if object.get_object_type() == ObjectType.Email:
await self.__embedding_email(object)
else:
pass
async def parse(self, object: ObjectID) -> str:
obj = self.env.get_knowledge_store().load_object(object)
await self.__do_embedding(obj)
return str(object)
def init(env: KnowledgePipelineEnvironment, params: dict) -> EmbeddingParser:
return EmbeddingParser(env, params)
@@ -0,0 +1,6 @@
name = "Mia"
input.module = "input.py"
input.params.path = "${myai_dir}/data"
parser.module = "parser.py"
parser.params.path = "${myai_dir}/knowledge/indices/embedding"
+96
View File
@@ -0,0 +1,96 @@
import os
import logging
import json
from aios import *
class EmbeddingEnvironment(SimpleEnvironment):
def __init__(self, workspace: str) -> None:
super().__init__(workspace)
self.path = os.path.join(AIStorage.get_instance().get_myai_dir(), "knowledge/indices/embedding")
self._default_text_model = "all-MiniLM-L6-v2"
self._default_image_model = "clip-ViT-B-32"
query_param = {
"tokens": "key words to query",
"types": "prefered knowledge types, one or more of [text, image]",
"limit": "index of query result"
}
self.add_ai_function(SimpleAIFunction("query_knowledge",
"vector query content from local knowledge base",
self._query,
query_param))
def __get_vector_store(self, model_name: str) -> ChromaVectorStore:
return ChromaVectorStore(self.path, model_name)
async def query_objects(self, tokens: str, types: list[str], topk: int) -> [ObjectID]:
texts = []
if "text" in types:
vector = await ComputeKernel.get_instance().do_text_embedding(tokens, self._default_text_model)
texts = await self.__get_vector_store(self._default_text_model).query(vector, topk)
images = []
if "image" in types:
vector = await ComputeKernel.get_instance().do_text_embedding(tokens, self._default_image_model)
images = await self.__get_vector_store(self._default_image_model).query(vector, topk)
return texts + images
def tokens_from_objects(self, object_ids: [ObjectID]) -> list[str]:
results = dict()
for object_id in object_ids:
parents = KnowledgeStore().get_relation_store().get_related_root_objects(object_id)
# last parent is the root object
root_object_id = parents[0] if parents else object_id
logging.info(f"object_id: {str(object_id)} root_object_id: {str(root_object_id)}")
if str(root_object_id) in results:
results[str(root_object_id)].append(object_id)
else:
results[str(root_object_id)] = [root_object_id, object_id]
content = ""
result_desc = []
for result in results.values():
# first element in result is the root object
root_object_id = result[0]
if root_object_id.get_object_type() == ObjectType.Email:
email = KnowledgeStore().load_object(root_object_id)
desc = email.get_desc()
desc["type"] = "email"
desc["contents"] = []
result_desc.append(desc)
upper_list = desc["contents"]
result = result[1:]
else:
upper_list = result_desc
for object_id in result:
if object_id.get_object_type() == ObjectType.Chunk:
upper_list.append({"type": "text", "content": KnowledgeStore().get_chunk_reader().get_chunk(object_id).read().decode("utf-8")})
if object_id.get_object_type() == ObjectType.Image:
# image = self.load_object(object_id)
desc = dict()
desc["id"] = str(object_id)
desc["type"] = "image"
upper_list.append(desc)
if object_id.get_object_type() == ObjectType.Video:
video = KnowledgeStore().load_object(object_id)
desc = video.get_desc()
desc["type"] = "video"
upper_list.append(desc)
else:
pass
content += json.dumps(result_desc)
content += ".\n"
return content
async def _query(self, tokens: str, types: list[str] = ["text"], index: str=0):
index = int(index)
object_ids = await self.query_objects(tokens, types, 4)
if len(object_ids) <= index:
return "*** I have no more information for your reference.\n"
else:
content = "*** I have provided the following known information for your reference with json format:\n"
return content + self.tokens_from_objects(object_ids[index:index+1])
def init(workspace: str) -> EmbeddingEnvironment:
return EmbeddingEnvironment(workspace)
@@ -0,0 +1,3 @@
pipelines = [
"JarvisPlus"
]
+39
View File
@@ -0,0 +1,39 @@
from .proto.agent_msg import *
from .proto.compute_task import *
from .proto.ai_function import *
from .proto.agent_task import *
from .agent.agent_base import *
from .agent.chatsession import AIChatSession
from .agent.agent import AIAgent,AIAgentTemplete, BaseAIAgent
from .agent.role import AIRole,AIRoleGroup
from .agent.workflow import Workflow
from .agent.agent_memory import AgentMemory
from .agent.workspace import AgentWorkspace
from .agent.llm_context import LLMProcessContext,GlobaToolsLibrary,SimpleLLMContext
from .agent.llm_process import BaseLLMProcess,LLMAgentBaseProcess
from .agent.llm_process_loader import LLMProcessLoader
from .frame.compute_kernel import ComputeKernel,ComputeTask,ComputeTaskResult,ComputeTaskState,ComputeTaskType
from .frame.compute_node import ComputeNode,LocalComputeNode
from .frame.bus import AIBus
from .frame.tunnel import AgentTunnel
from .frame.contact_manager import ContactManager,Contact
from .frame.queue_compute_node import Queue_ComputeNode
from .environment.environment import BaseEnvironment,SimpleEnvironment,CompositeEnvironment
# from .environment.workflow_env import WorkflowEnvironment,CalenderEnvironment,CalenderEvent,PaintEnvironment
from .ai_functions.text_to_speech_function import TextToSpeechFunction
from .ai_functions.image_2_text_function import Image2TextFunction
from .environment.workspace_env import WorkspaceEnvironment
from .storage.storage import ResourceLocation,AIStorage,UserConfig,UserConfigItem
from .net import *
from .knowledge import *
from .package_manager import *
from .utils import *
AIOS_Version = "0.5.2, build 2023-12-15"
+476
View File
@@ -0,0 +1,476 @@
import traceback
from typing import Optional
from asyncio import Queue
import asyncio
import logging
import uuid
import time
import json
import shlex
import datetime
import copy
import sys
from ..proto.agent_msg import AgentMsg
from ..proto.ai_function import *
from ..proto.agent_task import AgentTaskState,AgentTask,AgentTodo, AgentTodoState
from ..proto.compute_task import *
from .agent_base import *
from .llm_process import *
from .llm_process_loader import *
from .llm_do_task import *
from .chatsession import *
from ..environment.workspace_env import WorkspaceEnvironment, TodoListType
from ..environment.environment import *
from ..storage.storage import AIStorage
from ..knowledge import *
from ..proto.compute_task import LLMPrompt,LLMResult
logger = logging.getLogger(__name__)
class AIAgentTemplete:
def __init__(self) -> None:
self.llm_model_name:str = "gpt-4-turbo-preview"
self.max_token_size:int = 0
self.template_id:str = None
self.introduce:str = None
self.author:str = None
self.prompt:LLMPrompt = None
def load_from_config(self,config:dict) -> bool:
if config.get("llm_model_name") is not None:
self.llm_model_name = config["llm_model_name"]
if config.get("max_token_size") is not None:
self.max_token_size = config["max_token_size"]
if config.get("template_id") is not None:
self.template_id = config["template_id"]
if config.get("prompt") is not None:
self.prompt = LLMPrompt()
if self.prompt.load_from_config(config["prompt"]) is False:
logger.error("load prompt from config failed!")
return False
class AIAgent(BaseAIAgent):
def __init__(self) -> None:
self.role_prompt:LLMPrompt = None
self.agent_prompt:LLMPrompt = None
self.agent_think_prompt:LLMPrompt = None
self.llm_model_name:str = None
self.max_token_size:int = 128000
self.agent_energy = 15
self.agent_task = None
self.last_recover_time = time.time()
self.enable_thread = False
self.can_do_unassigned_task = True
self.agent_id:str = None
self.template_id:str = None
self.fullname:str = None
self.powerby = None
self.enable = True
self.enable_kb = False
self.enable_timestamp = False
self.guest_prompt_str = None
self.owner_promp_str = None
self.contact_prompt_str = None
self.history_len = 10
self.read_report_prompt = None
todo_prompts = {}
todo_prompts[TodoListType.TO_WORK] = {
"do": None,
"check": None,
"review": None,
}
todo_prompts[TodoListType.TO_LEARN] = {
"do": None,
"check": None,
"review": None,
}
self.todo_prompts = todo_prompts
self.base_dir = None
#self.memory_db = None
self.unread_msg = Queue() # msg from other agent
self.owenr_bus = None
self.memory : AgentMemory = None
self.prviate_workspace : AgentWorkspace = None
self.behaviors:Dict[str,BaseLLMProcess] = {}
async def initial(self,params:Dict = None):
self.base_dir = f"{AIStorage.get_instance().get_myai_dir()}/agent_data/{self.agent_id}"
memory_base_dir = f"{self.base_dir}/memory"
self.memory = AgentMemory(self.agent_id,memory_base_dir)
self.prviate_workspace = AgentWorkspace(self.agent_id)
init_params = {}
init_params["memory"] = self.memory
init_params["workspace"] = self.prviate_workspace
for process_name in self.behaviors.keys():
init_result = await self.behaviors[process_name].initial(init_params)
if init_result is False:
logger.error(f"llm process {process_name} initial failed! initial return False")
return False
self.wake_up()
return True
async def load_from_config(self,config:dict) -> bool:
if config.get("instance_id") is None:
logger.error("agent instance_id is None!")
return False
self.agent_id = config["instance_id"]
if config.get("fullname") is None:
logger.error(f"agent {self.agent_id} fullname is None!")
return False
self.fullname = config["fullname"]
if config.get("enable_thread") is not None:
self.enable_thread = bool(config["enable_thread"])
if config.get("powerby") is not None:
self.powerby = config["powerby"]
if config.get("template_id") is not None:
self.template_id = config["template_id"]
if config.get("llm_model_name") is not None:
self.llm_model_name = config["llm_model_name"]
if config.get("max_token_size") is not None:
self.max_token_size = config["max_token_size"]
if config.get("enable_function") is not None:
self.enable_function_list = config["enable_function"]
if config.get("enable_kb") is not None:
self.enable_kb = bool(config["enable_kb"])
if config.get("enable_timestamp") is not None:
self.enable_timestamp = bool(config["enable_timestamp"])
if config.get("history_len"):
self.history_len = int(config.get("history_len"))
#load all LLMProcess
self.behaviors = {}
behaviors = config.get("behavior")
for process_config_name in behaviors.keys():
process_config = behaviors[process_config_name]
real_config = {}
real_config.update(config)
real_config.update(process_config)
load_result = await LLMProcessLoader.get_instance().load_from_config(real_config)
if load_result:
self.behaviors[process_config_name] = load_result
else:
logger.error(f"load LLMProcess {process_config_name} failed!")
return False
return True
def get_id(self) -> str:
return self.agent_id
def get_fullname(self) -> str:
return self.fullname
def get_template_id(self) -> str:
return self.template_id
def get_llm_model_name(self) -> str:
if self.llm_model_name is None:
return AIStorage.get_instance().get_user_config().get_value("llm_model_name")
return self.llm_model_name
def get_max_token_size(self) -> int:
return self.max_token_size
def get_agent_role_prompt(self) -> LLMPrompt:
return self.role_prompt
def get_agent_prompt(self) -> LLMPrompt:
return self.agent_prompt
async def _get_context_info(self) -> Dict:
context_info = {}
context_info["location"] = "SanJose"
context_info["now"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
context_info["weather"] = "Partly Cloudy, 60°F"
context_info["owner"] = AIStorage.get_instance().get_user_config().get_value("username")
return context_info
async def llm_process_msg(self,msg:AgentMsg) -> AgentMsg:
need_process:bool = True
if msg.msg_type == AgentMsgType.TYPE_GROUPMSG:
need_process = False
session_topic = msg.target + "#" + msg.topic
chatsession = AIChatSession.get_session(self.agent_id,session_topic,self.memory.memory_db)
if msg.mentions is not None:
if self.agent_id in msg.mentions:
need_process = True
logger.info(f"agent {self.agent_id} recv a group chat message from {msg.sender},but is not mentioned,ignore!")
if need_process is not True:
chatsession.append(msg)
resp_msg = msg.create_group_resp_msg(self.agent_id,"")
return resp_msg
context_info = await self._get_context_info()
input_parms = {
"msg":msg,
"context_info":context_info
}
msg_process = self.behaviors.get("on_message")
llm_result : LLMResult = await msg_process.process(input_parms)
if llm_result.state == LLMResultStates.ERROR:
error_resp = msg.create_error_resp(llm_result.error_str)
return error_resp
elif llm_result.state == LLMResultStates.IGNORE:
return None
else: # OK
if llm_result.raw_result is not None:
resp_msg = llm_result.raw_result.get("_resp_msg")
return resp_msg
else:
return msg.create_resp_msg(llm_result.resp)
async def _process_msg(self,msg:AgentMsg,workspace = None) -> AgentMsg:
return await self.llm_process_msg(msg)
async def llm_self_think(self):
llm_process : BaseLLMProcess = self.behaviors.get("self_thinking")
if llm_process:
logger.info(f"agent {self.agent_id} self thinking start!")
context_info = await self._get_context_info()
#chat_history = AIChatSession.list_session(self.agent_id,self.memory.memory_db)
#known_experience_list = await self.memory.list_experience()
#record_list = await self.memory.load_records(await self.memory.get_last_think_time())
input_parms = {
"context_info":context_info
}
llm_result : LLMResult = await llm_process.process(input_parms)
if llm_result.state == LLMResultStates.ERROR:
logger.error(f"llm process self thinking error:{llm_result.compute_error_str}")
elif llm_result.state == LLMResultStates.IGNORE:
logger.info(f"llm process self thinking ignore!")
else:
logger.info(f"llm process self thinking ok!,think is:{llm_result.resp}")
await self.memory.set_last_think_time(time.time())
self.agent_energy -= 2
return
async def llm_triage_tasklist(self):
llm_process : BaseLLMProcess = self.behaviors.get("triage_tasks")
if llm_process:
if self.prviate_workspace:
filter = {}
filter["state"] = AgentTaskState.TASK_STATE_WAIT
tasklist:List[AgentTask]= await self.prviate_workspace.task_mgr.list_task(filter)
if tasklist:
if len(tasklist) > 0:
simple_list:List[Dict] = []
for task in tasklist:
simple_list.append(task.to_simple_dict())
input_parms = {
"tasklist":simple_list,
"context_info": await self._get_context_info()
}
llm_result : LLMResult = await llm_process.process(input_parms)
if llm_result.state == LLMResultStates.ERROR:
logger.error(f"llm process triage_tasks error:{llm_result.compute_error_str}")
elif llm_result.state == LLMResultStates.IGNORE:
logger.info(f"llm process triage_tasks ignore!")
else:
logger.info(f"llm process triage_tasks ok!,think is:{llm_result.resp}")
self.agent_energy -= 3
# for agent_task in tasklist:
# if self.agent_energy <= 0:
# break
# if agent_task.state == AgentTaskState.TASK_STATE_WAIT:
# input_parms = {
# "task":agent_task
# }
# llm_result : LLMResult = await llm_process.process(input_parms)
# if llm_result.state == LLMResultStates.ERROR:
# logger.error(f"llm process review_task error:{llm_result.error_str}")
# continue
# elif llm_result.state == LLMResultStates.IGNORE:
# logger.info(f"llm process review_task ignore!")
# continue
# else:
# determine = llm_result.raw_result.get("determine")
# logger.info(f"llm process review_task ok!,think is:{determine}")
# self.agent_energy -= 1
async def llm_do_todo(self, todo: AgentTodo):
llm_process : BaseLLMProcess = self.behaviors.get("do")
logger.info(f"agent {self.agent_id} DO todo {todo.todo_path} start!")
if llm_process:
input_parms = {
"todo":todo,
"context_info": await self._get_context_info()
}
llm_result : LLMResult = await llm_process.process(input_parms)
if llm_result.state == LLMResultStates.ERROR:
logger.error(f"llm process do_todo error:{llm_result.compute_error_str}")
elif llm_result.state == LLMResultStates.IGNORE:
logger.info(f"llm process do_todo ignore!")
else:
logger.info(f"llm process do_todo ok!,think is:{llm_result.resp}")
self.agent_energy -= 1
async def llm_check_todo(self, todo: AgentTodo):
llm_process : BaseLLMProcess = self.behaviors.get("check")
logger.info(f"agent {self.agent_id} CHECK todo {todo.todo_path} start!")
if llm_process:
input_parms = {
"todo":todo,
"context_info": await self._get_context_info()
}
llm_result : LLMResult = await llm_process.process(input_parms)
if llm_result.state == LLMResultStates.ERROR:
logger.error(f"llm process check_todo error:{llm_result.compute_error_str}")
elif llm_result.state == LLMResultStates.IGNORE:
logger.info(f"llm process check_todo ignore!")
else:
logger.info(f"llm process check_todo ok!,think is:{llm_result.resp}")
self.agent_energy -= 1
return
async def llm_plan_task(self,task:AgentTask):
llm_process : BaseLLMProcess = self.behaviors.get("plan_task")
logger.info(f"agent {self.agent_id} PLAN task {task.task_path} start!")
if llm_process:
input_parms = {
"task":task,
"context_info": await self._get_context_info()
}
llm_result : LLMResult = await llm_process.process(input_parms)
if llm_result.state == LLMResultStates.ERROR:
logger.error(f"llm process plan_task error:{llm_result.compute_error_str}")
elif llm_result.state == LLMResultStates.IGNORE:
logger.info(f"llm process plan_task ignore!")
else:
logger.info(f"llm process plan_task ok!,think is:{llm_result.resp}")
self.agent_energy -= 1
async def llm_review_task(self,task:AgentTask):
llm_process : BaseLLMProcess = self.behaviors.get("review_task")
logger.info(f"agent {self.agent_id} REVIEW task {task.task_path} start!")
if llm_process:
input_parms = {
"task":task,
"context_info": await self._get_context_info()
}
llm_result : LLMResult = await llm_process.process(input_parms)
if llm_result.state == LLMResultStates.ERROR:
logger.error(f"llm process review_task error:{llm_result.compute_error_str}")
elif llm_result.state == LLMResultStates.IGNORE:
logger.info(f"llm process review_task ignore!")
else:
logger.info(f"llm process review_task ok!,think is:{llm_result.resp}")
self.agent_energy -= 1
async def _self_imporve(self):
await self.llm_self_think()
def wake_up(self) -> None:
if self.agent_task is None:
self.agent_task = asyncio.create_task(self._on_timer())
else:
logger.warning(f"agent {self.agent_id} is already wake up!")
async def _on_timer(self):
await asyncio.sleep(5)
while True:
try:
now = time.time()
if self.last_recover_time is None:
self.last_recover_time = now
else:
if now - self.last_recover_time > 60:
self.agent_energy += (now - self.last_recover_time) / 60
self.last_recover_time = now
logger.info(f"agent {self.agent_id} recover energy to {self.agent_energy}")
if self.agent_energy <= 1:
logger.info(f"agent {self.agent_id} energy is too low!, goto sleep!")
continue
await self.llm_triage_tasklist()
# Get un finished tasks
#filter = {}
#filter["state"] = AgentTaskState.TASK_STATE_WAIT
filter = None
task_list:List[AgentTask] = await self.prviate_workspace.task_mgr.list_task(filter)
for task in task_list:
if self.agent_energy <= 0:
break
task = await self.prviate_workspace.task_mgr.get_task(task.task_id)
if task.can_plan():
# PLAN Task
await self.llm_plan_task(task)
task = await self.prviate_workspace.task_mgr.get_task(task.task_id)
if task.state == AgentTaskState.TASK_STATE_DOING:
# DO or Check Todo
can_review = False
todolist = await self.prviate_workspace.task_mgr.get_sub_todos(task.task_id)
for todo in todolist:
if self.agent_energy <= 0:
break
task = await self.prviate_workspace.task_mgr.get_task(task.task_id)
todo = await self.prviate_workspace.task_mgr.get_todo_by_id(todo.todo_id)
if task.state != AgentTaskState.TASK_STATE_DOING:
break
if todo.state == AgentTodoState.TODO_STATE_WAITING or todo.state == AgentTodoState.TODO_STATE_EXEC_FAILED:
await self.llm_do_todo(todo)
task = await self.prviate_workspace.task_mgr.get_task(task.task_id)
todo = await self.prviate_workspace.task_mgr.get_todo_by_id(todo.todo_id)
if task.state != AgentTaskState.TASK_STATE_DOING:
break
if todo.state == AgentTodoState.TODO_STATE_EXEC_OK:
await self.llm_check_todo(todo)
if can_review:
task.state = AgentTaskState.TASK_STATE_WAITING_REVIEW
task = await self.prviate_workspace.task_mgr.get_task(task.task_id)
if task.state == AgentTaskState.TASK_STATE_WAITING_REVIEW:
await self.llm_review_task(task)
await self._self_imporve()
except Exception as e:
tb_str = traceback.format_exc()
logger.error(f"agent {self.agent_id} on timer error:{e},{tb_str}")
# Because the LLM itself is very slow, the accuracy of the system processing task is in minutes.
await asyncio.sleep(30)
+38
View File
@@ -0,0 +1,38 @@
# pylint:disable=E0402
import abc
from abc import abstractmethod
from ..proto.agent_msg import AgentMsg
class BaseAIAgent(abc.ABC):
@abstractmethod
def get_id(self) -> str:
pass
@abstractmethod
def get_llm_model_name(self) -> str:
pass
@abstractmethod
def get_max_token_size(self) -> int:
pass
@abstractmethod
async def _process_msg(self,msg:AgentMsg,workspace = None) -> AgentMsg:
pass
class CustomAIAgent(BaseAIAgent):
def __init__(self, agent_id: str, llm_model_name: str, max_token_size: int) -> None:
self.agent_id = agent_id
self.llm_model_name = llm_model_name
self.max_token_size = max_token_size
def get_id(self) -> str:
return self.agent_id
def get_llm_model_name(self) -> str:
return self.llm_model_name
def get_max_token_size(self) -> int:
return self.max_token_size
+507
View File
@@ -0,0 +1,507 @@
# pylint:disable=E0402
from datetime import datetime,timedelta
import json
import os
import threading
from typing import Dict, List
import sqlite3
import aiofiles
from ..storage.storage import AIStorage
from ..frame.compute_kernel import ComputeKernel
from ..frame.contact_manager import ContactManager
from ..frame.contact import Contact
from ..proto.ai_function import ParameterDefine, SimpleAIAction, SimpleAIFunction
from ..proto.agent_msg import AgentMsg, AgentMsgType
from ..proto.agent_task import AgentWorkLog
from .llm_context import GlobaToolsLibrary
from .chatsession import AIChatSession
import logging
logger = logging.getLogger(__name__)
#class ObjectSummary:
# def __init__(self) -> None:
# self.summary : str = None
# self.object_name : str = None
# self.priority : int = 5
# [info_source, info]
# self.infos : Dict[str,str] = {}
class AgentMemory:
def __init__(self,agent_id:str,base_dir:str) -> None:
self.agent_memory_base_dir = base_dir
self.agent_id:str= agent_id
AIStorage.get_instance().ensure_directory_exists(self.agent_memory_base_dir)
AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/experience")
AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/contacts")
AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/relations")
AIStorage.get_instance().ensure_directory_exists(f"{self.agent_memory_base_dir}/summary")
self.memory_db:str = f"{self.agent_memory_base_dir}/memory.db"
self.model_name:str = "gp4-1106-preview"
self.threshold_hours = 72
self.last_think_time : float = 0.0
self.load_memory_meta()
def _get_conn(self):
""" get db connection """
local = threading.local()
if not hasattr(local, 'conn'):
local.conn = self._create_connection(self.memory_db)
return local.conn
def _create_connection(self, db_file):
""" create a database connection to a SQLite database """
conn = None
try:
conn = sqlite3.connect(db_file)
except Error as e:
logging.error("Error occurred while connecting to database: %s", e)
return None
if conn:
self._create_table(conn)
return conn
def get_session_from_msg(self,msg:AgentMsg) -> AIChatSession:
if msg.msg_type == AgentMsgType.TYPE_GROUPMSG:
session_topic = msg.target + "#" + msg.topic
chatsession = AIChatSession.get_session(self.agent_id,session_topic,self.memory_db)
else:
session_topic = msg.get_sender() + "#" + msg.topic
chatsession = AIChatSession.get_session(self.agent_id,session_topic,self.memory_db)
return chatsession
# return last record time
async def load_records(self,starttime,tokenlimit=8000)->float:
# 专用思路:做聊天记录/工作经验的整理
# 通用思路:没有具体的目的,让Agent根据提示词自己工作(可能效果很差也可能很好)
# 先实现通用思路
msg_records = AIChatSession.load_message_records_by_agentid(self.agent_id,starttime,32,self.memory_db)
work_records = self.load_worklogs(self.agent_id,token_limit=tokenlimit)
pass
async def load_chatlogs(self,msg:AgentMsg,token_limit=800):
chatsession = self.get_session_from_msg(msg)
# Must load n (n> = 2), and hope to load the M
# The information in the # M is gradually added, knowing that it is less than 72 hours from the current time, and consumes enough tokens
messages_n = chatsession.read_history() # read
histroy_str = ""
read_count = 0
is_all = True
for msg in messages_n:
dt = datetime.fromtimestamp(float(msg.create_time))
formatted_time = dt.strftime('%y-%m-%d %H:%M:%S')
record_str = f"{msg.sender},[{formatted_time}]\n{msg.body}\n"
token_limit -= ComputeKernel.llm_num_tokens_from_text(record_str,self.model_name)
if token_limit <= 32:
is_all = False
break
read_count += 1
histroy_str = record_str + histroy_str
return histroy_str,is_all
async def get_chat_summary(self,msg:AgentMsg) -> str:
chatsession : AIChatSession = self.get_session_from_msg(msg)
return chatsession.summary
# async def action_chatlog_append(self,params:Dict) -> str:
#
# input_msg:AgentMsg = params.get("input").get("msg")
# llm_result = params.get("llm_result")
# chatsession = self.get_session_from_msg(input_msg)
# resp_msg = params.get("resp_msg")
# if resp_msg:
# tags = llm_result.raw_result.get("tags")
# chatsession.append(input_msg,tags)
# chatsession.append(resp_msg,tags)
# return "OK"
async def load_worklogs(self,operator_id:str,owner_id:str=None, work_types:List[str]=None,token_limit=800):
conn = self._get_conn()
c = conn.cursor()
query = 'SELECT * FROM worklog WHERE 1=1'
params = []
if operator_id is not None:
query += ' AND operator=?'
params.append(operator_id)
if owner_id is not None:
query += ' AND owner_id=?'
params.append(owner_id)
if work_types:
query += ' AND work_type IN ({})'.format(', '.join('?'*len(work_types)))
params.extend(work_types)
query += ' ORDER BY timestamp DESC LIMIT 8'
c.execute(query, tuple(params))
rows = c.fetchall()
return [self.from_db_row(row) for row in rows]
def _create_table(self,conn):
c = conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS worklog (
logid TEXT PRIMARY KEY,
owner_id TEXT,
work_type TEXT,
timestamp REAL,
content TEXT,
result TEXT,
meta TEXT,
operator TEXT
)
''')
conn.commit()
#conn.close()
@classmethod
def from_db_row(self,row):
log = AgentWorkLog()
# 这里高度依赖表结构的顺序
log.logid, log.owner_id, log.work_type, log.timestamp, log.content, log.result, meta_str, log.operator = row
log.meta = json.loads(meta_str) if meta_str else None
return log
async def append_worklog(self,log:AgentWorkLog)->str:
conn = self._get_conn()
c = conn.cursor()
# 将meta字典转换为JSON字符串
meta_str = json.dumps(log.meta,ensure_ascii=False) if log.meta else None
c.execute('''
INSERT INTO worklog (logid, owner_id, work_type, timestamp, content, result, meta, operator)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (log.logid, log.owner_id, log.work_type, log.timestamp, log.content, log.result, meta_str, log.operator))
conn.commit()
#conn.close()
def memory_meta_to_dict(self) -> Dict:
return {
"last_think_time" : self.last_think_time
}
def load_meta(self,Dict):
self.last_think_time = Dict.get("last_think_time",0.0)
def load_memory_meta(self):
meta_file_path = f"{self.agent_memory_base_dir}/meta.json"
try:
with open(meta_file_path, mode='r') as file:
meta = json.load(file)
self.load_meta(meta)
except Exception as e:
logger.error(f"load memory meta failed: {e}")
self.last_think_time = 0.0
def save_memory_meta(self):
meta_file_path = f"{self.agent_memory_base_dir}/meta.json"
try:
with open(meta_file_path, mode='w') as file:
meta = self.memory_meta_to_dict()
json.dump(meta,file)
except Exception as e:
logger.error(f"save memory meta failed: {e}")
async def get_last_think_time(self)->float:
return self.last_think_time
async def set_last_think_time(self,last_time:float):
self.last_think_time = last_time
self.save_memory_meta()
async def get_contact_summary(self,contact_id:str) -> str:
if contact_id is None:
return "Contact id is None"
result = {}
contact_info:Contact = ContactManager.get_instance().find_contact_by_name(contact_id)
if contact_info:
result["name"] = contact_info.name
result["relation"] = contact_info.relationship
result["notes"] = contact_info.notes
summary_path = f"{self.agent_memory_base_dir}/contacts/{contact_id}.summary"
try:
async with aiofiles.open(summary_path, mode='r') as file:
result["summary"] = await file.read()
except Exception as e:
logger.error(f"read contact summary failed: {e}")
return json.dumps(result,ensure_ascii=False)
async def update_contact_summary(self,contact_id:str,summary:str):
summary_path = f"{self.agent_memory_base_dir}/contacts/{contact_id}.summary"
try:
async with aiofiles.open(summary_path, mode='w') as file:
await file.write(summary)
return "OK"
except Exception as e:
logger.error(f"write contact summary failed: {e}")
return "write contact summary failed: {e}"
async def get_summary(self,object_name:str) -> str:
summary_path = f"{self.agent_memory_base_dir}/{object_name}.summary"
try:
async with aiofiles.open(summary_path, mode='r') as file:
return await file.read()
except Exception as e:
logger.error(f"read summary failed: {e}")
return f"read summary failed: {e}"
async def update_summary(self,object_name:str,summary:str) -> str:
summary_path = f"{self.agent_memory_base_dir}/{object_name}.summary"
try:
async with aiofiles.open(summary_path, mode='w') as file:
await file.write(summary)
return "OK"
except Exception as e:
logger.error(f"write summary failed: {e}")
return f"write summary failed: {e}"
async def list_summary_object_names(self) -> List[str]:
# list dir
try:
contents = os.listdir(self.agent_memory_base_dir)
return [x for x in contents if x.endswith(".summary")]
except Exception as e:
logger.error(f"list summary object names failed: {e}")
return []
# means object1 feel object2 is ...
async def get_relation_summary(self,object_name1:str,object_name2:str) -> str:
summary_path = f"{self.agent_memory_base_dir}/relations/{object_name1}.relation.{object_name2}.summary"
try:
async with aiofiles.open(summary_path, mode='r') as file:
await file.read()
except FileNotFoundError:
return "no summary"
except Exception as e:
logger.error(f"read relation summary failed: {e}")
return f"read relation summary failed: {e}"
async def update_relation_summary(self,object_name1:str,object_name2:str,summary:Dict):
summary_path = f"{self.agent_memory_base_dir}/relations/{object_name1}.relation.{object_name2}.summary"
try:
async with aiofiles.open(summary_path, mode='w') as file:
await file.write(json.dumps(summary))
return "OK"
except Exception as e:
logger.error(f"write relation summary failed: {e}")
return "write relation summary failed: {e}"
async def get_experience(self,topic_name:str) -> str:
experience_path = f"{self.agent_memory_base_dir}/experience/{topic_name}.experience"
try:
async with aiofiles.open(experience_path, mode='r') as file:
await file.read()
except FileNotFoundError:
return "no experience"
except Exception as e:
logger.error(f"read experience failed: {e}")
return f"read experience failed: {e}"
async def set_experience(self,topic_name:str,summary:str) -> str:
experience_path = f"{self.agent_memory_base_dir}/experience/{topic_name}.experience"
try:
async with aiofiles.open(experience_path, mode='w') as file:
await file.write(summary)
return "OK"
except Exception as e:
logger.error(f"write experience failed: {e}")
return "write experience failed: {e}"
async def list_experience(self) -> List[str]:
dir_path = f"{self.agent_memory_base_dir}/experience"
try:
contents = os.listdir(dir_path)
return [x for x in contents if x.endswith(".experience")]
except Exception as e:
logger.error(f"list experience failed: {e}")
return []
@staticmethod
def register_ai_functions():
async def update_chat_summary(parameters):
agent_memory:AgentMemory = parameters.get("_memory")
chatsession = AIChatSession.get_session_by_id(parameters.get("session_id"),agent_memory.memory_db)
summary = parameters.get("summary")
chatsession.update_summary(summary)
return "OK"
parameters = ParameterDefine.create_parameters({
"session_id": {"type": "string", "description": "session id"},
"summary": {"type": "string", "description": "new summary"}
})
update_chat_summary_func = SimpleAIFunction("agent.memory.update_chat_summary",
"update chat summary",
update_chat_summary,
parameters)
GlobaToolsLibrary.get_instance().register_tool_function(update_chat_summary_func)
# async def get_contact_summary(parameters):
# agent_memory:AgentMemory = parameters.get("_memory")
# contact_name = parameters.get("contact_name")
# return await agent_memory.get_contact_summary(contact_name)
# parameters = ParameterDefine.create_parameters({
# "contact_name": {"type": "string", "description": "contact name"}
# })
# get_contact_summary_func = SimpleAIFunction("agent.memory.get_contact_summary",
# "get contact summary",
# get_contact_summary,
# parameters)
# GlobaToolsLibrary.register_tool_function(get_contact_summary_func)
# async def update_contact_summary(parameters):
# agent_memory:AgentMemory = parameters.get("_memory")
# contact_name = parameters.get("contact_name")
# summary = parameters.get("summary")
# return await agent_memory.update_contact_summary(contact_name,summary)
# parameters = ParameterDefine.create_parameters({
# "contact_name": {"type": "string", "description": "contact name"},
# "summary": {"type": "string", "description": "new summary"}
# })
# update_contact_summary_func = SimpleAIFunction("agent.memory.update_contact_summary",
# "update contact summary",
# update_contact_summary,
# parameters)
# GlobaToolsLibrary.register_tool_function(update_contact_summary_func)
# async def get_summary(parameters):
# agent_memory:AgentMemory = parameters.get("_memory")
# object_name = parameters.get("object_name")
# return await agent_memory.get_summary(object_name)
# parameters = ParameterDefine.create_parameters({
# "object_name": {"type": "string", "description": "object name"}
# })
# get_summary_func = SimpleAIFunction("agent.memory.get_summary",
# "get summary of sth",
# get_summary,
# parameters)
# GlobaToolsLibrary.register_tool_function(get_summary_func)
# async def update_summary(parameters):
# agent_memory:AgentMemory = parameters.get("_memory")
# object_name = parameters.get("object_name")
# summary = parameters.get("summary")
# return await agent_memory.update_summary(object_name,summary)
# parameters = ParameterDefine.create_parameters({
# "object_name": {"type": "string", "description": "object name"},
# "summary": {"type": "string", "description": "new summary"}
# })
# update_summary_func = SimpleAIFunction("agent.memory.update_summary",
# "update summary of sth",
# update_summary,
# parameters)
# GlobaToolsLibrary.register_tool_function(update_summary_func)
# async def list_summary_object_names(parameters):
# agent_memory:AgentMemory = parameters.get("_memory")
# return await agent_memory.list_summary_object_names()
# parameters = ParameterDefine.create_parameters({})
# list_summary_object_names_func = SimpleAIFunction("agent.memory.list_summary",
# "list summary object names",
# list_summary_object_names,
# parameters)
# GlobaToolsLibrary.register_tool_function(list_summary_object_names_func)
# async def get_relation_summary(parameters):
# agent_memory:AgentMemory = parameters.get("_memory")
# object_name1 = parameters.get("object1_name")
# object_name2 = parameters.get("object2_name")
# return await agent_memory.get_relation_summary(object_name1,object_name2)
# parameters = ParameterDefine.create_parameters({
# "object1_name": {"type": "string", "description": "object name1"},
# "object2_name": {"type": "string", "description": "object name2"}
# })
# get_relation_summary_func = SimpleAIFunction("agent.memory.get_relation_summary",
# "object1 feel object2 is ...",
# get_relation_summary,
# parameters)
# GlobaToolsLibrary.register_tool_function(get_relation_summary_func)
# async def update_relation_summary(parameters):
# agent_memory:AgentMemory = parameters.get("_memory")
# object_name1 = parameters.get("object1_name")
# object_name2 = parameters.get("object2_name")
# summary = parameters.get("summary")
# return await agent_memory.update_relation_summary(object_name1,object_name2,summary)
# parameters = ParameterDefine.create_parameters({
# "object1_name": {"type": "string", "description": "object name1"},
# "object2_name": {"type": "string", "description": "object name2"},
# "summary": {"type": "string", "description": "new summary"}
# })
# update_relation_summary_func = SimpleAIFunction("agent.memory.update_relation_summary",
# "object1 feel object2 is ...",
# update_relation_summary,
# parameters)
# GlobaToolsLibrary.register_tool_function(update_relation_summary_func)
# async def get_experience(parameters):
# agent_memory:AgentMemory = parameters.get("_memory")
# topic_name = parameters.get("topic_name")
# return await agent_memory.get_experience(topic_name)
# parameters = ParameterDefine.create_parameters({
# "topic_name": {"type": "string", "description": "topic name"}
# })
# get_experience_func = SimpleAIFunction("agent.memory.get_experience",
# "get experience",
# get_experience,
# parameters)
# GlobaToolsLibrary.register_tool_function(get_experience_func)
# async def set_experience(parameters):
# agent_memory:AgentMemory = parameters.get("_memory")
# topic_name = parameters.get("topic_name")
# summary = parameters.get("summary")
# return await agent_memory.set_experience(topic_name,summary)
# parameters = ParameterDefine.create_parameters({
# "topic_name": {"type": "string", "description": "topic name"},
# "summary": {"type": "string", "description": "new summary"}
# })
# set_experience_func = SimpleAIFunction("agent.memory.set_experience",
# "set experience",
# set_experience,
# parameters)
# GlobaToolsLibrary.register_tool_function(set_experience_func)
# async def list_experience(parameters):
# agent_memory:AgentMemory = parameters.get("_memory")
# return await agent_memory.list_experience()
# parameters = ParameterDefine.create_parameters({})
# list_experience_func = SimpleAIFunction("agent.memory.list_experience",
# "list exist experience topics",
# list_experience,
# parameters)
# GlobaToolsLibrary.register_tool_function(list_experience_func)
@@ -1,4 +1,4 @@
# pylint:disable=E0402
import sqlite3 # Because sqlite3 IO operation is small, so we can use sqlite3 directly.(so we don't need to use async sqlite3 now) import sqlite3 # Because sqlite3 IO operation is small, so we can use sqlite3 directly.(so we don't need to use async sqlite3 now)
from sqlite3 import Error from sqlite3 import Error
import logging import logging
@@ -6,21 +6,22 @@ import threading
import datetime import datetime
import uuid import uuid
import json import json
from typing import List
from .agent_message import AgentMsgType, AgentMsg, AgentMsgStatus from ..proto.agent_msg import AgentMsgType, AgentMsg, AgentMsgStatus
class ChatSessionDB: class ChatSessionDB:
def __init__(self, db_file): def __init__(self, db_file):
""" initialize db connection """ """ initialize db connection """
self.local = threading.local()
self.db_file = db_file self.db_file = db_file
self._get_conn() self._get_conn()
def _get_conn(self): def _get_conn(self):
""" get db connection """ """ get db connection """
if not hasattr(self.local, 'conn'): local = threading.local()
self.local.conn = self._create_connection(self.db_file) if not hasattr(local, 'conn'):
return self.local.conn local.conn = self._create_connection(self.db_file)
return local.conn
def _create_connection(self, db_file): def _create_connection(self, db_file):
""" create a database connection to a SQLite database """ """ create a database connection to a SQLite database """
@@ -37,9 +38,10 @@ class ChatSessionDB:
return conn return conn
def close(self): def close(self):
if not hasattr(self.local, 'conn'): local = threading.local()
if not hasattr(local, 'conn'):
return return
self.local.conn.close() local.conn.close()
def _create_table(self, conn): def _create_table(self, conn):
""" create table """ """ create table """
@@ -52,7 +54,8 @@ class ChatSessionDB:
SessionTopic TEXT, SessionTopic TEXT,
StartTime TEXT, StartTime TEXT,
SummarizePos INTEGER, SummarizePos INTEGER,
Summary TEXT Summary TEXT,
ThreadID TEXT
); );
""") """)
@@ -82,28 +85,29 @@ class ChatSessionDB:
ActionResult TEXT, ActionResult TEXT,
DoneTime TEXT, DoneTime TEXT,
Status INTEGER Status INTEGER,
Tags TEXT
); );
""") """)
conn.commit() conn.commit()
except Error as e: except Error as e:
logging.error("Error occurred while creating tables: %s", e) logging.error("Error occurred while creating tables: %s", e)
def insert_chatsession(self, session_id, session_owner,session_topic, start_time): def insert_chatsession(self, session_id, session_owner,session_topic, start_time,thread_id = ""):
""" insert a new session into the ChatSessions table """ """ insert a new session into the ChatSessions table """
try: try:
conn = self._get_conn() conn = self._get_conn()
conn.execute(""" conn.execute("""
INSERT INTO ChatSessions (SessionID, SessionOwner,SessionTopic, StartTime,SummarizePos,Summary) INSERT INTO ChatSessions (SessionID, SessionOwner,SessionTopic, StartTime,SummarizePos,Summary,ThreadID)
VALUES (?,?, ?, ?,0,"") VALUES (?,?, ?, ?,0,"",?)
""", (session_id, session_owner,session_topic, start_time)) """, (session_id, session_owner,session_topic, start_time,thread_id))
conn.commit() conn.commit()
return 0 # return 0 if successful return 0 # return 0 if successful
except Error as e: except Error as e:
logging.error("Error occurred while inserting session: %s", e) logging.error("Error occurred while inserting session: %s", e)
return -1 # return -1 if an error occurs return -1 # return -1 if an error occurs
def insert_message(self, msg:AgentMsg): def insert_message(self, msg:AgentMsg,tags:List[str] = None):
""" insert a new message into the Messages table """ """ insert a new message into the Messages table """
try: try:
action_name = None action_name = None
@@ -111,29 +115,31 @@ class ChatSessionDB:
action_result = None action_result = None
mentions = None mentions = None
if msg.mentions: if msg.mentions:
mentions = json.dumps(msg.mentions) mentions = json.dumps(msg.mentions,ensure_ascii=False)
match msg.msg_type: match msg.msg_type:
case AgentMsgType.TYPE_MSG: case AgentMsgType.TYPE_MSG:
pass pass
case AgentMsgType.TYPE_ACTION: case AgentMsgType.TYPE_ACTION:# THIS Action is not AIAction
action_name = msg.func_name action_name = msg.func_name
action_params = json.dumps(msg.args) action_params = json.dumps(msg.args,ensure_ascii=False)
action_result = msg.result_str action_result = msg.result_str
case AgentMsgType.TYPE_INTERNAL_CALL: case AgentMsgType.TYPE_INTERNAL_CALL:
action_name = msg.func_name action_name = msg.func_name
action_params = json.dumps(msg.args) action_params = json.dumps(msg.args,ensure_ascii=False)
action_result = msg.result_str action_result = msg.result_str
case AgentMsgType.TYPE_EVENT: case AgentMsgType.TYPE_EVENT:
action_name = msg.event_name action_name = msg.event_name
action_params = json.dumps(msg.event_args) action_params = json.dumps(msg.event_args,ensure_ascii=False)
if tags is None:
tags = []
str_tags = ','.join(tags)
conn = self._get_conn() conn = self._get_conn()
conn.execute(""" conn.execute("""
INSERT INTO Messages (MessageID, SessionID, MsgType, PrevMsgID, SenderID, ReceiverID, Timestamp, Topic,Mentions,ContentMIME,Content,ActionName,ActionParams,ActionResult,DoneTime,Status) INSERT INTO Messages (MessageID, SessionID, MsgType, PrevMsgID, SenderID, ReceiverID, Timestamp, Topic,Mentions,ContentMIME,Content,ActionName,ActionParams,ActionResult,DoneTime,Status,Tags)
VALUES (?, ?, ?, ?, ?, ?, ?,?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?,?, ?, ?, ?, ?, ?, ?, ?, ?,?)
""", (msg.msg_id, msg.session_id, msg.msg_type.value, msg.prev_msg_id, msg.sender, msg.target, msg.create_time, msg.topic,mentions,msg.body_mime,msg.body,action_name,action_params,action_result,msg.done_time,msg.status.value)) """, (msg.msg_id, msg.session_id, msg.msg_type.value, msg.prev_msg_id, msg.sender, msg.target, msg.create_time, msg.topic,mentions,msg.body_mime,msg.body,action_name,action_params,action_result,msg.done_time,msg.status.value,str_tags))
conn.commit() conn.commit()
if msg.inner_call_chain: if msg.inner_call_chain:
@@ -192,6 +198,9 @@ class ChatSessionDB:
try: try:
conn = self._get_conn() conn = self._get_conn()
cursor = conn.cursor() cursor = conn.cursor()
if limit == 0:
limit = 1024
cursor.execute(""" cursor.execute("""
SELECT MessageID, SessionID, MsgType, PrevMsgID, SenderID, ReceiverID, Timestamp, Topic,Mentions,ContentMIME,Content,ActionName,ActionParams,ActionResult,DoneTime,Status FROM Messages SELECT MessageID, SessionID, MsgType, PrevMsgID, SenderID, ReceiverID, Timestamp, Topic,Mentions,ContentMIME,Content,ActionName,ActionParams,ActionResult,DoneTime,Status FROM Messages
WHERE SessionID = ? WHERE SessionID = ?
@@ -205,12 +214,31 @@ class ChatSessionDB:
logging.error("Error occurred while getting messages: %s", e) logging.error("Error occurred while getting messages: %s", e)
return -1, None # return -1 and None if an error occurs return -1, None # return -1 and None if an error occurs
def load_message_by_agentid(self,agent_id,limit,start_time="1970-01-01 00:00:00"):
try:
conn = self._get_conn()
cursor = conn.cursor()
cursor.execute("""
SELECT MessageID, SessionID, MsgType, PrevMsgID, SenderID, ReceiverID, Timestamp, Topic,Mentions,ContentMIME,Content,ActionName,ActionParams,ActionResult,DoneTime,Status FROM Messages
WHERE SenderID = ? or ReceiverID =? AND Timestamp > ?
ORDER BY Timestamp
LIMIT ?
""", (agent_id, agent_id, start_time,limit))
results = cursor.fetchall()
#self.close()
return results # return 0 and the result if successful
except Error as e:
logging.error("Error occurred while getting messages: %s", e)
return -1, None
# read message from now->beign # read message from now->beign
def get_messages(self, session_id, limit, offset): def get_messages(self, session_id, limit, offset):
""" retrieve messages of a session with pagination """ """ retrieve messages of a session with pagination """
try: try:
conn = self._get_conn() conn = self._get_conn()
cursor = conn.cursor() cursor = conn.cursor()
if limit == 0:
limit = 1024
cursor.execute(""" cursor.execute("""
SELECT MessageID, SessionID, MsgType, PrevMsgID, SenderID, ReceiverID, Timestamp, Topic,Mentions,ContentMIME,Content,ActionName,ActionParams,ActionResult,DoneTime,Status FROM Messages SELECT MessageID, SessionID, MsgType, PrevMsgID, SenderID, ReceiverID, Timestamp, Topic,Mentions,ContentMIME,Content,ActionName,ActionParams,ActionResult,DoneTime,Status FROM Messages
WHERE SessionID = ? WHERE SessionID = ?
@@ -254,10 +282,26 @@ class ChatSessionDB:
logging.error("Error occurred while updating session summary: %s", e) logging.error("Error occurred while updating session summary: %s", e)
return -1 return -1
def update_session_thread_id(self, session_id, thread_id):
""" update the threadid of a session """
try:
conn = self._get_conn()
conn.execute("""
UPDATE ChatSessions
SET ThreadID = ?
WHERE SessionID = ?
""", (thread_id, session_id))
conn.commit()
return 0 # return 0 if successful
except Error as e:
logging.error("Error occurred while updating session threadid: %s", e)
return -1
# chat session store the chat history between owner and agent # chat session store the chat history between owner and agent
# chat session might be large, so can read / write at stream mode. # chat session might be large, so can read / write at stream mode.
class AIChatSession: class AIChatSession:
_dbs = {} _dbs = {}
_sessions = {}
#@classmethod #@classmethod
#async def get_session_by_id(cls,session_id:str,db_path:str): #async def get_session_by_id(cls,session_id:str,db_path:str):
# db = cls._dbs.get(db_path) # db = cls._dbs.get(db_path)
@@ -266,6 +310,38 @@ class AIChatSession:
# cls._dbs[db_path] = db # cls._dbs[db_path] = db
# db.get_chatsession_by_id(session_id) # db.get_chatsession_by_id(session_id)
# #result = AIChatSession() # #result = AIChatSession()
@classmethod
# start_time is a string like "2021-01-01 00:00:00"
def load_message_records_by_agentid(cls,agent_id:str,start_time:str,limit:int,db_path:str)->List[AgentMsg]:
db = cls._dbs.get(db_path)
if db is None:
db = ChatSessionDB(db_path)
cls._dbs[db_path] = db
msgs = db.load_message_by_agentid(agent_id,start_time,limit)
result = []
for msg in msgs:
agent_msg = AgentMsg()
agent_msg.msg_id = msg[0]
agent_msg.session_id = msg[1]
agent_msg.msg_type = AgentMsgType(msg[2])
agent_msg.prev_msg_id = msg[3]
agent_msg.sender = msg[4]
agent_msg.target = msg[5]
agent_msg.create_time = msg[6]
agent_msg.topic = msg[7]
if msg[8] is not None:
agent_msg.mentions = json.loads(msg[8])
agent_msg.body_mime = msg[9]
agent_msg.body = msg[10]
agent_msg.func_name = msg[11]
if msg[12] is not None:
agent_msg.args = json.loads(msg[12])
agent_msg.result_str = msg[13]
agent_msg.done_time = msg[14]
agent_msg.status = AgentMsgStatus(msg[15])
result.append(agent_msg)
return result
@classmethod @classmethod
def get_session(cls,owner_id:str,session_topic:str,db_path:str,auto_create = True) -> 'AIChatSession': def get_session(cls,owner_id:str,session_topic:str,db_path:str,auto_create = True) -> 'AIChatSession':
@@ -275,17 +351,24 @@ class AIChatSession:
cls._dbs[db_path] = db cls._dbs[db_path] = db
result = None result = None
for session in cls._sessions.values():
if session.owner_id == owner_id and session.topic == session_topic:
return session
session = db.get_chatsession_by_owner_topic(owner_id,session_topic) session = db.get_chatsession_by_owner_topic(owner_id,session_topic)
if session is None: if session is None:
if auto_create: if auto_create:
session_id = "CS#" + uuid.uuid4().hex session_id = "CS#" + uuid.uuid4().hex
db.insert_chatsession(session_id,owner_id,session_topic,datetime.datetime.now()) db.insert_chatsession(session_id,owner_id,session_topic,datetime.datetime.now())
result = AIChatSession(owner_id,session_id,db) result = AIChatSession(owner_id,session_id,db)
cls._sessions[session_id] = result
else: else:
result = AIChatSession(owner_id,session[0],db) result = AIChatSession(owner_id,session[0],db)
result.topic = session_topic result.topic = session_topic
result.summarize_pos = session[4] result.summarize_pos = session[4]
result.summary = session[5] result.summary = session[5]
result.openai_thread_id = session[6]
cls._sessions[result.session_id] = result
return result return result
@@ -297,6 +380,10 @@ class AIChatSession:
cls._dbs[db_path] = db cls._dbs[db_path] = db
result = None result = None
session = cls._sessions.get(session_id)
if session:
return session
session = db.get_chatsession_by_id(session_id) session = db.get_chatsession_by_id(session_id)
if session is None: if session is None:
return None return None
@@ -305,6 +392,8 @@ class AIChatSession:
result.topic = session[2] result.topic = session[2]
result.summarize_pos = session[4] result.summarize_pos = session[4]
result.summary = session[5] result.summary = session[5]
result.openai_thread_id = session[6]
cls._sessions[session_id] = result
return result return result
@@ -330,12 +419,13 @@ class AIChatSession:
self.topic : str = None self.topic : str = None
self.start_time : str = None self.start_time : str = None
self.summarize_pos : int = 0 self.summarize_pos : int = 0
self.summary = None self.summary : str = None
self.openai_thread_id = None
def get_owner_id(self) -> str: def get_owner_id(self) -> str:
return self.owner_id return self.owner_id
def read_history(self, number:int=10,offset=0,order="revers") -> [AgentMsg]: def read_history(self, number:int=0,offset=0,order="revers") -> [AgentMsg]:
if order == "revers": if order == "revers":
msgs = self.db.get_messages(self.session_id, number, offset) msgs = self.db.get_messages(self.session_id, number, offset)
else: else:
@@ -366,16 +456,19 @@ class AIChatSession:
result.append(agent_msg) result.append(agent_msg)
return result return result
def append(self,msg:AgentMsg) -> None: def append(self,msg:AgentMsg,tags:List[str] = None) -> None:
msg.session_id = self.session_id msg.session_id = self.session_id
self.db.insert_message(msg) self.db.insert_message(msg,tags)
def update_think_progress(self,progress:int,new_summary:str) -> None: def update_summary(self,new_summary:str) -> None:
self.db.update_session_summary(self.session_id,progress,new_summary) self.db.update_session_summary(self.session_id,self.summarize_pos,new_summary)
self.summarize_pos = progress
self.summary = new_summary self.summary = new_summary
def update_openai_thread_id(self,thread_id:str) -> None:
self.db.update_session_thread_id(self.session_id,thread_id)
self.openai_thread_id = thread_id
#def attach_event_handler(self,handler) -> None: #def attach_event_handler(self,handler) -> None:
# """chat session changed event handler""" # """chat session changed event handler"""
# pass # pass
+316
View File
@@ -0,0 +1,316 @@
# pylint:disable=E0402
from abc import ABC, abstractmethod
import json
import logging
from typing import Optional,Set,List,Dict,Callable
from ..proto.ai_function import AIFunction,AIAction, AIFunction2Action,SimpleAIAction
logger = logging.getLogger(__name__)
class LLMProcessContext:
def __init__(self) -> None:
pass
@staticmethod
def function2action(ai_func:AIFunction) -> AIAction:
return AIFunction2Action(ai_func)
@staticmethod
def aifunctions_to_inner_functions(all_inner_function:List[AIFunction]) -> List[Dict]:
if all_inner_function is None:
return []
result_func = []
result_len = 0
for inner_func in all_inner_function:
func_name = inner_func.get_name()
this_func = {}
this_func["name"] = func_name
this_func["description"] = inner_func.get_description()
this_func["parameters"] = inner_func.get_openai_parameters()
result_len += len(json.dumps(this_func,ensure_ascii=False)) / 4
result_func.append(this_func)
return result_func
@abstractmethod
def get_ai_function(self,func_name:str) -> AIFunction:
pass
def get_all_ai_functions(self) -> List[AIFunction]:
return self.get_function_set(None)
@abstractmethod
def get_function_set(self,set_name:str = None) -> List[AIFunction]:
pass
@abstractmethod
def get_ai_action(self,op_name:str) -> AIAction:
pass
def get_all_ai_action(self) -> List[AIAction]:
return self.get_action_set(None)
@abstractmethod
def get_action_set(self,set_name:str = None) -> List[AIFunction]:
pass
def __getitem__(self, key):
return self.get_value(key)
@abstractmethod
def get_value(self,key:str) -> Optional[str]:
pass
#def list_actions(self,path:str) -> List[AIAction]:
# return "No more actions!"
#def list_functions(self,path:str) -> List[AIFunction]:
# return "No more tool functions!"
class GlobaToolsLibrary:
_instance = None
@classmethod
def get_instance(cls) -> 'GlobaToolsLibrary':
if cls._instance is None:
cls._instance = GlobaToolsLibrary()
return cls._instance
def __init__(self,global_env_name:str = None) -> None:
self.all_preset_context = {}
self.all_tool_functions : Dict[str,AIFunction] = {}
self.all_action_sets : Dict[str,Set[str]] = {}
self.all_function_sets : Dict[str,Set[str]] = {}
def register_prset_context(self,preset_id:str,context) -> None:
self.all_preset_context[preset_id] = context
def get_preset_context(self,preset_id:str):
return self.all_preset_context.get(preset_id)
def register_tool_function(self,function:AIFunction) -> None:
if self.all_tool_functions.get(function.get_id()):
logger.warning(f"Tool function {function.get_id()} already exists! will be replaced!")
self.all_tool_functions[function.get_id()] = function
def get_tool_function(self,function_name:str) -> AIFunction:
return self.all_tool_functions.get(function_name)
def register_function_set(self,set_name:str,function_set:Set[str]) -> None:
self.all_function_sets[set_name] = function_set
def get_function_set(self,set_name:str) -> Set[str]:
return self.all_function_sets.get(set_name)
class SimpleLLMContext(LLMProcessContext):
def __init__(self) -> None:
super().__init__()
self.parent = None
self.values : Dict[str,str] = {}
self.values_callback = {}
self.functions: Dict[str,AIFunction] = {}
self.func_sets : Dict[str,Dict[str,AIFunction]] = {}
self.actions: Dict[str,AIAction] = {}
self.action_sets : Dict[str,Dict[str,AIAction]] = {}
def load_action_set_from_config(self,preset,config:Dict[str,str]) -> Dict:
if preset is None:
result = {}
else:
result = preset
enable_actions = config.get("enable")
if enable_actions:
for action_id in enable_actions:
ai_func = GlobaToolsLibrary.get_instance().get_tool_function(action_id)
if ai_func:
result[action_id] = LLMProcessContext.function2action(ai_func)
else:
func_set = GlobaToolsLibrary.get_instance().get_function_set(action_id)
if func_set:
for _func_id in func_set:
ai_func = GlobaToolsLibrary.get_instance().get_tool_function(_func_id)
if ai_func:
result[_func_id] = LLMProcessContext.function2action(ai_func)
else:
logger.error(f"load_action_set_from_config failed! enable action id {action_id} not found!")
return None
disable_actions = config.get("disable")
if disable_actions:
for disable_action in disable_actions:
if result.get(disable_action):
result.pop(disable_action)
else:
func_set = GlobaToolsLibrary.get_instance().get_function_set(action_id)
if func_set:
for _func_id in func_set:
if result.get(_func_id):
result.pop(_func_id)
else:
logger.error(f"load_action_set_from_config failed! disable action id {action_id} not found!")
return None
return result
def load_function_set_from_config(self,preset,config:Dict) -> Dict[str,AIFunction]:
if preset is None:
result = {}
else:
result = preset
enable_functions = config.get("enable")
if enable_functions:
for func_id in enable_functions:
ai_func = GlobaToolsLibrary.get_instance().get_tool_function(func_id)
if ai_func:
result[func_id] = ai_func
else:
func_set = GlobaToolsLibrary.get_instance().get_function_set(func_id)
if func_set:
for func_id in func_set:
ai_func = GlobaToolsLibrary.get_instance().get_tool_function(func_id)
if ai_func:
result[func_id] = ai_func
else:
logger.error(f"load_function_set_from_config failed! enable function id {func_id} not found!")
return None
else:
logger.error(f"load_function_set_from_config failed! enable function id {func_id} not found!")
return None
disable_functions = config.get("disable")
if disable_functions:
for disable_function in disable_functions:
if result.get(disable_function):
result.pop(disable_function)
else:
func_set = GlobaToolsLibrary.get_instance().get_function_set(func_id)
if func_set:
for func_id in func_set:
if result.get(func_id):
result.pop(func_id)
else:
logger.error(f"load_function_set_from_config failed! disable function id {disable_function} not found!")
return None
return result
def load_from_config(self,config:Dict[str,str]) -> bool:
preset = config.get("preset")
if preset:
self.parent:SimpleLLMContext = GlobaToolsLibrary.get_instance().get_preset_context(preset)
if self.parent is None:
logger.error(f"preset context {preset} not found!")
return False
self.values = self.parent.values
self.values_callback = self.parent.values_callback
self.actions = self.parent.actions
self.functions = self.parent.functions
self.action_sets = self.parent.action_sets
self.func_sets = self.parent.func_sets
action_def:Dict= config.get("actions")
if action_def:
self.actions = self.load_action_set_from_config(self.actions,action_def)
if self.actions is None:
logger.error(f"load_from_config failed! load_action_set_from_config failed!")
return False
for set_name in action_def.keys():
if set_name == "enable":
continue
if set_name == "disable":
continue
sub_set = config.get(set_name)
self.action_sets[set_name] = self.load_action_set_from_config(None,sub_set)
if self.action_sets[set_name] is None:
logger.error(f"load_from_config failed! load_action_set_from_config failed!")
return False
function_def:Dict = config.get("functions")
if function_def:
self.functions = self.load_function_set_from_config(self.functions,function_def)
if self.functions is None:
logger.error(f"load_from_config failed! load_function_set_from_config failed!")
return False
for set_name in function_def.keys():
if set_name == "enable":
continue
if set_name == "disable":
continue
sub_set = config.get(set_name)
self.func_sets[set_name] = self.load_function_set_from_config(None,sub_set)
if self.func_sets[set_name] is None:
logger.error(f"load_from_config failed! load_function_set_from_config failed!")
return False
#values_def = config.get("values")
#if values_def:
# for key,value in values_def.items():
# self.values[key] = value
def get_value(self,key:str) -> Optional[str]:
callback = self.values_callback.get(key)
if callback:
return callback()
return self.values.get(key)
def set_value_callback(self,key:str,callback:Callable[[],str]) -> None:
self.values_callback[key] = callback
def set_value(self,key:str,value:str):
self.values[key] = value
def get_ai_function(self,func_name:str) -> AIFunction:
for func in self.functions.values():
if func.get_name() == func_name:
return func
#for set_name in self.func_sets.keys():
# func = self.func_sets[set_name].get(func_name)
# if func is not None:
# return func
return None
def get_function_set(self,set_name:str = None) -> List[AIFunction]:
if self.functions is None:
return None
if set_name is None:
return self.functions.values()
else:
func_set = self.func_sets.get(set_name)
if func_set:
return func_set.values()
return None
def get_ai_action(self,op_name:str) -> AIAction:
for action in self.actions.values():
if action.get_name() == op_name:
return action
return None
def get_action_set(self,set_name:str = None) -> List[AIFunction]:
if self.actions is None:
return None
if set_name is None:
return self.actions.values()
else:
action_set = self.action_sets.get(set_name)
if action_set:
return action_set.values()
return None
+373
View File
@@ -0,0 +1,373 @@
from ..proto.compute_task import LLMPrompt,LLMResult,ComputeTaskResult,ComputeTaskResultCode
from ..proto.ai_function import AIFunction,AIAction,ActionNode
from ..proto.agent_msg import AgentMsg,AgentMsgType
from ..proto.agent_task import AgentTask, AgentTodo, AgentWorkLog
from ..frame.compute_kernel import ComputeKernel
from .agent_memory import AgentMemory
from .workspace import AgentWorkspace
from .llm_context import LLMProcessContext,GlobaToolsLibrary, SimpleLLMContext
from .llm_process import BaseLLMProcess,LLMAgentBaseProcess
from abc import ABC,abstractmethod
import copy
import json
import datetime
from datetime import datetime
from typing import Any, Callable, Coroutine, Optional,Dict,Awaitable,List
from enum import Enum
import logging
logger = logging.getLogger(__name__)
#LLM Process All the unfinished tasks,will sort the priority of the task after LLM, determine the next execution time, and complete the simple task
class AgentTriageTaskList(LLMAgentBaseProcess):
def __init__(self) -> None:
super().__init__()
async def load_from_config(self,config:dict) -> bool:
if await super().load_from_config(config) is False:
return False
async def prepare_prompt(self,input:Dict) -> LLMPrompt:
prompt = LLMPrompt()
task_list:List[AgentTask] = input.get("tasklist")
context_info = input.get("context_info")
if task_list is None:
logger.error(f"tasklist not found in input")
return None
prompt.append_user_message(json.dumps(task_list,ensure_ascii=False))
system_prompt_dict = self.prepare_role_system_prompt(context_info)
# May all logs is good for Agent Triage Task List?
have_known_info = False
known_info = {}
working_logs = await self.memory.load_worklogs(self.memory.agent_id)
if len(working_logs) > 0:
have_known_info = True
all_worklog_node = []
for worklog in working_logs:
workNode = {}
dt = datetime.fromtimestamp(float(worklog.timestamp))
workNode["timestamp"] = dt.strftime("%Y-%m-%d %H:%M:%S")
workNode["type"] = worklog.work_type
workNode["content"] = worklog.content
workNode["result"] = worklog.result
all_worklog_node.append(workNode)
known_info["worklogs"] = all_worklog_node
if have_known_info:
system_prompt_dict["known_info"] = known_info
prompt.inner_functions =LLMProcessContext.aifunctions_to_inner_functions(self.llm_context.get_all_ai_functions())
prompt.append_system_message(json.dumps(system_prompt_dict,ensure_ascii=False))
return prompt
async def post_llm_process(self,actions:List[ActionNode],input:Dict,llm_result:LLMResult) -> bool:
action_params = {}
action_params["_input"] = input
action_params["_memory"] = self.memory
action_params["_workspace"] = self.workspace
action_params["_llm_result"] = llm_result
action_params["_agentid"] = self.memory.agent_id
action_params["_start_at"] = datetime.now()
result_str = "OK"
try:
if await self._execute_actions(actions,action_params) is False:
result_str = "execute action failed!"
except Exception as e:
logger.error(f"execute action failed! {e}")
result_str = "execute action failed!,error:" + str(e)
worklog = AgentWorkLog.create_by_content("","triage",llm_result.resp,self.memory.agent_id)
worklog.result = result_str
await self.memory.append_worklog(worklog)
# LLM a Task that never been LLMed, the result of LLM Process may be adjusted, splitting subtask or do simple task as a todo directly.
class AgentPlanTask(LLMAgentBaseProcess):
def __init__(self) -> None:
super().__init__()
async def load_from_config(self, config: dict,is_load_default=True) -> bool:
if await super().load_from_config(config) is False:
return False
async def prepare_prompt(self,input:Dict) -> LLMPrompt:
prompt = LLMPrompt()
agent_task : AgentTask= input.get("task")
context_info = input.get("context_info")
if agent_task is None:
logger.error(f"task not found in input")
return None
prompt.append_user_message(json.dumps(agent_task.to_dict(),ensure_ascii=False))
system_prompt_dict = self.prepare_role_system_prompt(context_info)
have_known_info = False
known_info = {}
working_logs = await self.memory.load_worklogs(None,agent_task.task_id)
if len(working_logs) > 0:
have_known_info = True
all_worklog_node = []
for worklog in working_logs:
workNode = {}
dt = datetime.fromtimestamp(float(worklog.timestamp))
workNode["timestamp"] = dt.strftime("%Y-%m-%d %H:%M:%S")
workNode["type"] = worklog.work_type
workNode["operator"] = worklog.operator
workNode["content"] = worklog.content
workNode["result"] = worklog.result
all_worklog_node.append(workNode)
known_info["worklogs"] = all_worklog_node
if have_known_info:
system_prompt_dict["known_info"] = known_info
prompt.inner_functions =LLMProcessContext.aifunctions_to_inner_functions(self.llm_context.get_all_ai_functions())
prompt.append_system_message(json.dumps(system_prompt_dict,ensure_ascii=False))
return prompt
async def post_llm_process(self,actions:List[ActionNode],input:Dict,llm_result:LLMResult) -> bool:
action_params = {}
action_params["_input"] = input
agent_task : AgentTask= input.get("task")
action_params["_memory"] = self.memory
action_params["_workspace"] = self.workspace
action_params["_llm_result"] = llm_result
action_params["_agentid"] = self.memory.agent_id
action_params["_start_at"] = datetime.now()
result_str = "OK"
try:
if await self._execute_actions(actions,action_params) is False:
result_str = "execute action failed!"
except Exception as e:
logger.error(f"execute action failed! {e}")
result_str = "execute action failed!,error:" + str(e)
worklog = AgentWorkLog.create_by_content(agent_task.task_id,"tackling",llm_result.resp,self.memory.agent_id)
worklog.result = result_str
await self.memory.append_worklog(worklog)
# Agent DO Todo
# The purpose is to complete Todo.It is the core LLM process. Can use sufficient external tools to do your best according to the identity and ability of AGENT.It is also the LLM Process of the main extension of Agent extension
class AgentDo(LLMAgentBaseProcess):
def __init__(self) -> None:
super().__init__()
async def load_from_config(self, config: dict,is_load_default=True) -> Coroutine[Any, Any, bool]:
if await super().load_from_config(config) is False:
return False
async def prepare_prompt(self,input:Dict) -> LLMPrompt:
prompt = LLMPrompt()
agent_todo : AgentTodo= input.get("todo")
context_info = input.get("context_info")
if agent_todo is None:
logger.error(f"task not found in input")
return None
prompt.append_user_message(json.dumps(agent_todo.to_dict(),ensure_ascii=False))
system_prompt_dict = self.prepare_role_system_prompt(context_info)
# May all logs is good for Agent Triage Task List?
have_known_info = False
known_info = {}
working_logs = await self.memory.load_worklogs(None,agent_todo.todo_id)
if len(working_logs) > 0:
have_known_info = True
all_worklog_node = []
for worklog in working_logs:
workNode = {}
dt = datetime.fromtimestamp(float(worklog.timestamp))
workNode["timestamp"] = dt.strftime("%Y-%m-%d %H:%M:%S")
workNode["type"] = worklog.work_type
workNode["content"] = worklog.content
workNode["result"] = worklog.result
all_worklog_node.append(workNode)
known_info["worklogs"] = all_worklog_node
if have_known_info:
system_prompt_dict["known_info"] = known_info
prompt.inner_functions =LLMProcessContext.aifunctions_to_inner_functions(self.llm_context.get_all_ai_functions())
prompt.append_system_message(json.dumps(system_prompt_dict,ensure_ascii=False))
return prompt
async def post_llm_process(self,actions:List[ActionNode],input:Dict,llm_result:LLMResult) -> bool:
action_params = {}
action_params["_input"] = input
agent_todo : AgentTodo= input.get("todo")
action_params["_memory"] = self.memory
action_params["_workspace"] = self.workspace
action_params["_llm_result"] = llm_result
action_params["_agentid"] = self.memory.agent_id
action_params["_start_at"] = datetime.now()
result_str = "OK"
try:
if await self._execute_actions(actions,action_params) is False:
result_str = "execute action failed!"
except Exception as e:
logger.error(f"execute action failed! {e}")
result_str = "execute action failed!,error:" + str(e)
worklog = AgentWorkLog.create_by_content(agent_todo.todo_id,"do",llm_result.resp,self.memory.agent_id)
worklog.result = result_str
await self.memory.append_worklog(worklog)
#Agent check todo
# LLM a already-DO TODO, the purpose is to check whether it is completed to face the illusion of LLM.Check can use some tools, which is also the core of the agent extension。
class AgentCheck(LLMAgentBaseProcess):
def __init__(self) -> None:
super().__init__()
async def load_from_config(self, config: dict,is_load_default=True) -> Coroutine[Any, Any, bool]:
if await super().load_from_config(config) is False:
return False
async def prepare_prompt(self,input:Dict) -> LLMPrompt:
prompt = LLMPrompt()
agent_todo : AgentTodo= input.get("todo")
context_info = input.get("context_info")
if agent_todo is None:
logger.error(f"task not found in input")
return None
prompt.append_user_message(json.dumps(agent_todo.to_dict(),ensure_ascii=False))
system_prompt_dict = self.prepare_role_system_prompt(context_info)
# May all logs is good for Agent Triage Task List?
have_known_info = False
known_info = {}
working_logs = await self.memory.load_worklogs(None,agent_todo.todo_id)
if len(working_logs) > 0:
have_known_info = True
all_worklog_node = []
for worklog in working_logs:
workNode = {}
dt = datetime.fromtimestamp(float(worklog.timestamp))
workNode["timestamp"] = dt.strftime("%Y-%m-%d %H:%M:%S")
workNode["type"] = worklog.work_type
workNode["content"] = worklog.content
workNode["result"] = worklog.result
all_worklog_node.append(workNode)
known_info["worklogs"] = all_worklog_node
if have_known_info:
system_prompt_dict["known_info"] = known_info
prompt.inner_functions =LLMProcessContext.aifunctions_to_inner_functions(self.llm_context.get_all_ai_functions())
prompt.append_system_message(json.dumps(system_prompt_dict,ensure_ascii=False))
return prompt
async def post_llm_process(self,actions:List[ActionNode],input:Dict,llm_result:LLMResult) -> bool:
action_params = {}
action_params["_input"] = input
agent_todo : AgentTodo= input.get("todo")
action_params["_memory"] = self.memory
action_params["_workspace"] = self.workspace
action_params["_llm_result"] = llm_result
action_params["_agentid"] = self.memory.agent_id
action_params["_start_at"] = datetime.now()
result_str = "OK"
try:
if await self._execute_actions(actions,action_params) is False:
result_str = "execute action failed!"
except Exception as e:
logger.error(f"execute action failed! {e}")
result_str = "execute action failed!,error:" + str(e)
worklog = AgentWorkLog.create_by_content(agent_todo.todo_id,"check",llm_result.resp,self.memory.agent_id)
worklog.result = result_str
await self.memory.append_worklog(worklog)
#Agent review task
#When Task's Todolist is completed, or Task's subtask is completed, LLM review a TASK to determine that the Task has been completed.This Review also failed to execute.
class AgentReviewTask(LLMAgentBaseProcess):
def __init__(self) -> None:
super().__init__()
async def load_from_config(self, config: dict,is_load_default=True) -> Coroutine[Any, Any, bool]:
if await super().load_from_config(config) is False:
return False
async def prepare_prompt(self,input:Dict) -> LLMPrompt:
prompt = LLMPrompt()
agent_task : AgentTask= input.get("task")
context_info = input.get("context_info")
if agent_task is None:
logger.error(f"task not found in input")
return None
prompt.append_user_message(json.dumps(agent_task.to_dict(),ensure_ascii=False))
system_prompt_dict = self.prepare_role_system_prompt(context_info)
# May all logs is good for Agent Triage Task List?
have_known_info = False
known_info = {}
working_logs = await self.memory.load_worklogs(None,agent_task.task_id)
if len(working_logs) > 0:
have_known_info = True
all_worklog_node = []
for worklog in working_logs:
workNode = {}
dt = datetime.fromtimestamp(float(worklog.timestamp))
workNode["timestamp"] = dt.strftime("%Y-%m-%d %H:%M:%S")
workNode["type"] = worklog.work_type
workNode["operator"] = worklog.operator
workNode["content"] = worklog.content
workNode["result"] = worklog.result
all_worklog_node.append(workNode)
known_info["worklogs"] = all_worklog_node
if have_known_info:
system_prompt_dict["known_info"] = known_info
prompt.inner_functions =LLMProcessContext.aifunctions_to_inner_functions(self.llm_context.get_all_ai_functions())
prompt.append_system_message(json.dumps(system_prompt_dict,ensure_ascii=False))
return prompt
async def post_llm_process(self,actions:List[ActionNode],input:Dict,llm_result:LLMResult) -> bool:
action_params = {}
action_params["_input"] = input
agent_task : AgentTask= input.get("task")
action_params["_memory"] = self.memory
action_params["_workspace"] = self.workspace
action_params["_llm_result"] = llm_result
action_params["_agentid"] = self.memory.agent_id
action_params["_start_at"] = datetime.now()
result_str = "OK"
try:
if await self._execute_actions(actions,action_params) is False:
result_str = "execute action failed!"
except Exception as e:
logger.error(f"execute action failed! {e}")
result_str = "execute action failed!,error:" + str(e)
worklog = AgentWorkLog.create_by_content(agent_task.task_id,"review",llm_result.resp,self.memory.agent_id)
worklog.result = result_str
await self.memory.append_worklog(worklog)
+652
View File
@@ -0,0 +1,652 @@
# Old name is behavior, I belive new name "llm_process" is better
# pylint:disable=E0402
import os.path
from .chatsession import AIChatSession
from ..utils import video_utils,image_utils
from ..proto.compute_task import LLMPrompt,LLMResult,ComputeTaskResult,ComputeTaskResultCode
from ..proto.ai_function import AIFunction,AIAction,ActionNode
from ..proto.agent_msg import AgentMsg,AgentMsgType
from .agent_memory import AgentMemory
from .workspace import AgentWorkspace
from .llm_context import LLMProcessContext,GlobaToolsLibrary, SimpleLLMContext
from ..frame.compute_kernel import ComputeKernel
from abc import ABC,abstractmethod
import copy
import json
import datetime
from datetime import datetime
from typing import Any, Callable, Coroutine, Optional,Dict,Awaitable,List
from enum import Enum
import logging
logger = logging.getLogger(__name__)
MIN_PREDICT_TOKEN_LEN = 32
class BaseLLMProcess(ABC):
def __init__(self) -> None:
self.behavior:str = None #行为名字
self.goal:str = None #目标
self.input_example:str= None #输入样例
self.result_example:str = None #llm_result样例
self.enable_json_resp = False
#None means system default,
# TODO: support abcstract model name like: local-hight,local-low,local-medium,remote-hight,remote-low,remote-medium
self.model_name = None
self.max_token = 2000 # result_token
self.max_prompt_token = 2000 # not include input prompt
self.chat_summary_token_len = 500
self.timeout = 1800 # 30 min
self.llm_context:LLMProcessContext = None
def get_llm_model_name(self) -> str:
return self.model_name
@abstractmethod
async def prepare_prompt(self,input:Dict) -> LLMPrompt:
pass
@abstractmethod
async def get_inner_function_for_exec(self,func_name:str) -> AIFunction:
pass
@abstractmethod
def prepare_inner_function_context_for_exec(self,inner_func_name:str,parameters:Dict):
return
@abstractmethod
async def post_llm_process(self,actions:List[ActionNode],input:Dict,llm_result:LLMResult) -> bool:
pass
def get_remain_prompt_length(self,prompt:LLMPrompt,will_append_str:str) -> int:
return self.max_prompt_token - ComputeKernel.llm_num_tokens(prompt,self.model_name)
@abstractmethod
async def load_from_config(self,config:dict) -> bool:
#self.behavior = config.get("behavior")
#self.goal = config.get("goal")
self.input_example = config.get("input_example")
self.result_example = config.get("result_example")
if config.get("model_name"):
self.model_name = config.get("model_name")
if config.get("enable_json_resp"):
self.enable_json_resp = config.get("enable_json_resp") == "true"
if config.get("max_token"):
self.max_token = config.get("max_token")
if config.get("timeout"):
self.timeout = config.get("timeout")
return True
@abstractmethod
async def initial(self,params:Dict = None) -> bool:
pass
def _format_content_by_env_value(self,content:str,env)->str:
return content.format_map(env)
async def _execute_inner_func(self,inner_func_call_node:Dict,prompt: LLMPrompt,stack_limit = 1) -> ComputeTaskResult:
arguments = None
stack_limit = stack_limit - 1
try:
func_name = inner_func_call_node.get("name")
arguments = json.loads(inner_func_call_node.get("arguments"))
logger.info(f"LLMProcess execute inner func:{func_name} :({json.dumps(arguments,ensure_ascii=False)})")
func_node : AIFunction = await self.get_inner_function_for_exec(func_name)
if func_node is None:
result_str:str = f"execute {func_name} error,function not found"
else:
self.prepare_inner_function_context_for_exec(func_name,arguments)
result_str:str = await func_node.execute(arguments)
except Exception as e:
result_str = f"execute {func_name} error:{str(e)}"
logger.error(f"LLMProcess execute inner func:{func_name} error:\n\t{e}")
logger.info("LLMProcess execute inner func result:" + result_str)
prompt.messages.append({"role":"function","content":result_str,"name":func_name})
if self.enable_json_resp:
resp_mode = "json"
else:
resp_mode = "text"
max_result_token = self.max_token - ComputeKernel.llm_num_tokens(prompt,self.model_name)
if max_result_token < MIN_PREDICT_TOKEN_LEN:
task_result = ComputeTaskResult()
task_result.result_code = ComputeTaskResultCode.ERROR
task_result.error_str = f"prompt too long,can not predict"
return task_result
if stack_limit > 0:
inner_functions=prompt.inner_functions
else:
inner_functions = None
task_result: ComputeTaskResult = await (ComputeKernel.get_instance().do_llm_completion(
prompt,
resp_mode=resp_mode,
mode_name=self.get_llm_model_name(),
max_token=max_result_token,
inner_functions=inner_functions, #NOTICE: inner_function in prompt can be a subset of get_inner_function
timeout=self.timeout))
if task_result.result_code != ComputeTaskResultCode.OK:
logger.error(f"llm compute error:{task_result.error_str}")
return task_result
inner_func_call_node = None
result_message : dict = task_result.result.get("message")
if result_message:
inner_func_call_node = result_message.get("function_call")
if inner_func_call_node:
func_msg = copy.deepcopy(result_message)
del func_msg["tool_calls"]#TODO: support tool_calls?
prompt.messages.append(func_msg)
if inner_func_call_node:
return await self._execute_inner_func(inner_func_call_node,prompt,stack_limit-1)
else:
return task_result
async def process(self,input:Dict) -> LLMResult:
if self.enable_json_resp:
resp_mode = "json"
else:
resp_mode = "text"
# Action define in prompt, will be execute after llm compute
prompt = await self.prepare_prompt(input)
if prompt is None:
logger.warn(f"prepare_prompt return None, break llm_process")
return LLMResult.from_error_str("prepare_prompt return None")
max_result_token = self.max_token - ComputeKernel.llm_num_tokens(prompt,self.get_llm_model_name())
#if max_result_token < MIN_PREDICT_TOKEN_LEN:
# return LLMResult.from_error_str(f"prompt too long,can not predict")
task_result: ComputeTaskResult = await (ComputeKernel.get_instance().do_llm_completion(
prompt,
resp_mode=resp_mode,
mode_name=self.get_llm_model_name(),
max_token=max_result_token,
inner_functions=prompt.inner_functions, #NOTICE: inner_function in prompt can be a subset of get_inner_function
timeout=self.timeout))
if task_result.result_code != ComputeTaskResultCode.OK:
err_str = f"do_llm_completion error:{task_result.error_str}"
logger.error(err_str)
return LLMResult.from_error_str(err_str)
result_message = task_result.result.get("message")
inner_func_call_node = None
if result_message:
inner_func_call_node = result_message.get("function_call")
if inner_func_call_node:
call_prompt : LLMPrompt = copy.deepcopy(prompt)
func_msg = copy.deepcopy(result_message)
del func_msg["tool_calls"]
call_prompt.messages.append(func_msg)
task_result = await self._execute_inner_func(inner_func_call_node,call_prompt)
# parse task_result to LLM Result
if self.enable_json_resp:
try:
llm_result = LLMResult.from_json_str(task_result.result_str)
except Exception as e:
logger.error(f"parse llm result error:{e}")
llm_result = LLMResult.from_str(task_result.result_str)
else:
llm_result = LLMResult.from_str(task_result.result_str)
# use action to save history?
await self.post_llm_process(llm_result.action_list,input,llm_result)
return llm_result
class LLMAgentBaseProcess(BaseLLMProcess):
def __init__(self) -> None:
super().__init__()
self.role_description:str = None
self.process_description:str = None
self.reply_format:str = None
self.context : str = None
self.workspace : AgentWorkspace = None # If Workspace is not none , enable Agent Tasklist
self.memory : AgentMemory = None
self.enable_kb : bool = False
self.kb = None
async def initial(self,params:Dict = None) -> bool:
self.memory = params.get("memory")
if self.memory is None:
logger.error(f"LLMAgeMessageProcess initial failed! memory not found")
return False
self.workspace = params.get("workspace")
return True
async def load_default_config(self) -> bool:
return True
async def load_from_config(self, config: dict,is_load_default=True) -> Coroutine[Any, Any, bool]:
if is_load_default:
await self.load_default_config()
if await super().load_from_config(config) is False:
return False
self.role_description = config.get("role_desc")
if self.role_description is None:
logger.error(f"role_description not found in config")
return False
if config.get("process_description"):
self.process_description = config.get("process_description")
if config.get("reply_format"):
self.reply_format = config.get("reply_format")
if config.get("context"):
self.context = config.get("context")
self.llm_context = SimpleLLMContext()
if config.get("llm_context"):
self.llm_context.load_from_config(config.get("llm_context"))
if config.get("enable_kb"):
self.enable_kb = config.get("enable_kb") == "true"
def prepare_role_system_prompt(self,context_info:Dict) -> Dict:
system_prompt_dict = {}
# System Prompt
## LLM的身份说明
system_prompt_dict["role_description"] = self.role_description
#prompt.append_system_message(self.role_description)
## 处理信息的流程说明
system_prompt_dict["process_rule"] = self.process_description
#prompt.append_system_message(self.process_description)
### 回复的格式
system_prompt_dict["reply_format"] = self.reply_format
#prompt.append_system_message(self.reply_format)
## Context
if self.context:
context = self._format_content_by_env_value(self.context,context_info)
system_prompt_dict["context"] = context
#prompt.append_system_message(context)
system_prompt_dict["support_actions"] = self.get_action_desc()
return system_prompt_dict
def prepare_inner_function_context_for_exec(self,inner_func_name:str,parameters:Dict):
parameters["_workspace"] = self.workspace
def get_action_desc(self) -> Dict:
result = {}
actions_list = self.llm_context.get_all_ai_action()
for action in actions_list:
result[action.get_name()] = action.get_description()
return result
async def get_inner_function_for_exec(self,func_name:str) -> AIFunction:
return self.llm_context.get_ai_function(func_name)
async def _execute_actions(self,actions:List[ActionNode],action_params:Dict):
for action_item in actions:
op : AIAction = self.llm_context.get_ai_action(action_item.name)
if op:
if action_item.parms is None:
action_item.parms = {}
real_parms = {**action_params,**action_item.parms}
action_item.parms["_result"] = await op.execute(real_parms)
action_item.parms["_end_at"] = datetime.now()
else:
logger.warn(f"action {action_item.name} not found")
return False
class AgentMessageProcess(LLMAgentBaseProcess):
def __init__(self) -> None:
super().__init__()
self.mutil_model = None
self.enable_media2text = False
self.is_mutil_model = False
self.asr_model = None
self.tts_model = None
async def load_default_config(self) -> bool:
return True
async def load_from_config(self, config: dict,is_load_default=True) -> Coroutine[Any, Any, bool]:
if is_load_default:
await self.load_default_config()
if await super().load_from_config(config) is False:
return False
self.enable_media2text = config.get('enable_media2text', 'false').lower() in ('true', '1', 't', 'y', 'yes')
if config.get("mutil_model"):
self.mutil_model = config.get("mutil_model")
self.asr_model = config.get("asr_model")
self.tts_model = config.get("tts_model")
def get_llm_model_name(self) -> str:
if self.is_mutil_model:
return self.mutil_model
else:
return self.model_name
def check_and_to_base64(self, image_path: str) -> str:
if image_utils.is_file(image_path):
return image_utils.to_base64(image_path, (1024, 1024))
else:
return image_path
async def get_prompt_from_msg(self,msg:AgentMsg) -> LLMPrompt:
msg_prompt = LLMPrompt()
self.is_mutil_model = False
if msg.is_image_msg():
if self.enable_media2text:
logger.error(f"enable_media2text is not supported yet")
else:
image_prompt, images = msg.get_image_body()
if image_prompt is None:
msg_prompt.messages = [{"role": "user", "content": [{"type": "image_url", "image_url": {"url": self.check_and_to_base64(image)}} for image in images]}]
else:
content = [{"type": "text", "text": image_prompt}]
content.extend([{"type": "image_url", "image_url": {"url": self.check_and_to_base64(image)}} for image in images])
msg_prompt.messages = [{"role": "user", "content": content}]
if self.mutil_model:
self.is_mutil_model = True
else:
logger.warning(f"mutil_model is not set!")
elif msg.is_video_msg():
if self.enable_media2text:
logger.error(f"enable_media2text is not supported yet")
else:
video_prompt, video = msg.get_video_body()
frames = video_utils.extract_frames(video, (1024, 1024))
audio_file = os.path.splitext(video)[0] + ".mp3"
video_utils.extract_audio(video, audio_file)
voice_content = None
if self.asr_model is not None:
resp = await (ComputeKernel.get_instance().do_speech_to_text(audio_file, model=self.asr_model, prompt=None, response_format="text"))
if resp.result_code == ComputeTaskResultCode.OK:
voice_content = resp.result_str
content = []
if video_prompt is not None:
content.append({"type": "text", "text": video_prompt})
if voice_content is not None and voice_content != "":
content.append({"type": "text", "text": f"Voice content in video:{voice_content}"})
content.extend([{"type": "image_url", "image_url": {"url": frame}} for frame in frames])
msg_prompt.messages = [{"role": "user", "content": content}]
if self.mutil_model:
self.is_mutil_model = True
else:
logger.warning(f"mutil_model is not set!")
elif msg.is_audio_msg():
if self.enable_media2text:
logger.error(f"enable_media2text is not supported yet")
else:
prompt, audio_file = msg.get_audio_body()
resp = await (ComputeKernel.get_instance().do_speech_to_text(audio_file, model=self.asr_model, prompt=None, response_format="text"))
if resp.result_code != ComputeTaskResultCode.OK:
error_resp = msg.create_error_resp(resp.error_str)
return error_resp
else:
if prompt == "":
msg.body = resp.result_str
msg_prompt.messages = [{"role":"user","content":resp.result_str}]
else:
msg.body = f"{prompt}\nVoice content:{resp.result_str}"
msg_prompt.messages = [{"role":"user","content": prompt}, {"role": "user", "content": f"Voice content:{resp.result_str}"}]
else:
msg_prompt.messages = [{"role":"user","content":msg.body}]
return msg_prompt
async def sender_info(self,msg:AgentMsg)->str:
sender_id = msg.sender
#TODO Is sender an agent?
return await self.memory.get_contact_summary(sender_id)
async def load_chatlogs(self,msg:AgentMsg,max_length_by_token:int)->str:
## like
#sender,[2023-11-1 12:00:00]
#content
return await self.memory.load_chatlogs(msg,max_length_by_token)
async def get_chat_summary(self,msg:AgentMsg)->str:
return await self.memory.get_chat_summary(msg)
async def get_extend_known_info(self,msg:AgentMsg,prompt:LLMPrompt)->str:
return None
async def prepare_prompt(self,input:Dict) -> LLMPrompt:
prompt = LLMPrompt()
# User Prompt
## Input Msg
msg : AgentMsg = input.get("msg")
context_info = input.get("context_info")
if msg is None:
logger.error(f"LLMAgeMessageProcess prepare_prompt failed! input msg not found")
return None
msg_prompt = await self.get_prompt_from_msg(msg)
if msg_prompt is None:
logger.error(f"LLMAgeMessageProcess prepare_prompt failed! get_prompt_from_msg return None")
return None
prompt.append(msg_prompt)
## 通用的角色相关的系统提示词
system_prompt_dict = self.prepare_role_system_prompt(context_info)
## 已知信息
known_info = {}
#prompt.append_system_message(self.known_info_tips)
### 信息发送者资料
known_info["sender_info"] = await self.sender_info(msg)
#prompt.append_system_message(await self.sender_info(self,msg))
system_prompt_dict["known_info"] = known_info
prompt.inner_functions =LLMProcessContext.aifunctions_to_inner_functions(self.llm_context.get_all_ai_functions())
if self.workspace:
#TODO eanble workspace functions?
logger.info(f"workspace is not none,enable workspace functions")
## 给予查询KB的权限
if self.enable_kb:
logger.info(f"enable kb")
### 根据Token Limit加载聊天记录
remain_token = self.get_remain_prompt_length(prompt,json.dumps(system_prompt_dict,ensure_ascii=False))
chat_record,is_all = await self.load_chatlogs(msg,remain_token - self.chat_summary_token_len)
if chat_record:
if len(chat_record) > 4:
known_info["chat_record"] = chat_record
if not is_all :
### 如果出触发了Token Limit,则删除几条信息后,加载summary (summary的长度基本是固定的)
summary = await self.get_chat_summary(msg)
if summary:
if len(summary) > 4:
known_info["chat_summary"] = summary
# TODO: extend known info
#prompt.append_system_message(await self.get_extend_known_info(msg,prompt))
prompt.append_system_message(json.dumps(system_prompt_dict,ensure_ascii=False))
return prompt
async def post_llm_process(self,actions:List[ActionNode],input:Dict,llm_result:LLMResult) -> bool:
msg:AgentMsg = input.get("msg")
if msg.msg_type == AgentMsgType.TYPE_GROUPMSG:
resp_msg = msg.create_group_resp_msg(self.memory.agent_id,llm_result.resp)
else:
resp_msg = msg.create_resp_msg(llm_result.resp)
if llm_result.raw_result is not None:
llm_result.raw_result["_resp_msg"] = resp_msg
action_params = {}
action_params["_input"] = input
action_params["_memory"] = self.memory
action_params["_workspace"] = self.workspace
action_params["_resp_msg"] = resp_msg
action_params["_llm_result"] = llm_result
action_params["_agentid"] = self.memory.agent_id
action_params["_start_at"] = datetime.now()
await self._execute_actions(actions,action_params)
chatsession = self.memory.get_session_from_msg(msg)
chatsession.append(msg)
chatsession.append(resp_msg)
return True
class AgentSelfThinking(LLMAgentBaseProcess):
def __init__(self) -> None:
super().__init__()
async def load_from_config(self, config: dict) -> Coroutine[Any, Any, bool]:
if await super().load_from_config(config) is False:
return False
async def _load_chat_history(self,token_limit:int):
chat_history = {}
session_list = AIChatSession.list_session(self.memory.agent_id ,self.memory.memory_db)
total_read_msg = 0
for session_id in session_list:
chatsession = AIChatSession.get_session_by_id(session_id,self.memory.memory_db)
session_history = {}
session_history["summary"] = chatsession.summary
session_history["id"] = chatsession.session_id
token_limit -= ComputeKernel.llm_num_tokens_from_text(chatsession.summary,self.model_name)
read_history_msg = 0
if token_limit > 8:
# load session chat history
cur_pos = chatsession.summarize_pos
messages = chatsession.read_history(0,cur_pos,"natural") # read
history_str = ""
for msg in messages:
read_history_msg += 1
total_read_msg += 1
cur_pos += 1
dt = datetime.fromtimestamp(float(msg.create_time))
formatted_time = dt.strftime('%y-%m-%d %H:%M:%S')
record_str = f"{msg.sender},[{formatted_time}]\n{msg.body}\n"
token_limit -= ComputeKernel.llm_num_tokens_from_text(record_str,self.model_name)
if token_limit < 8:
break
history_str = history_str + record_str
if read_history_msg >= 2:
session_history["history"] = history_str
chat_history[session_id] = session_history
chatsession.summarize_pos = cur_pos
else:
logger.info(f"load_chat_history reach token limit,load {total_read_msg} history messages.")
return chat_history
if total_read_msg < 2:
logger.info(f"load_chat_history: no history messages,return NONE")
return None
return chat_history
async def prepare_prompt(self,input:Dict) -> LLMPrompt:
prompt = LLMPrompt()
context_info = input.get("context_info")
system_prompt_dict = self.prepare_role_system_prompt(context_info)
# Known_info is the SESSION summary of the existence, the current task work record summary,
token_remain = self.get_remain_prompt_length(prompt,json.dumps(system_prompt_dict,ensure_ascii=False))
chat_history = await self._load_chat_history(token_remain)
if chat_history is None:
logger.info(f"prepare_prompt: no history messages,return NONE")
return None
prompt.inner_functions =LLMProcessContext.aifunctions_to_inner_functions(self.llm_context.get_all_ai_functions())
prompt.append_system_message(json.dumps(system_prompt_dict,ensure_ascii=False))
prompt.append_user_message(json.dumps(chat_history,ensure_ascii=False))
return prompt
async def post_llm_process(self,actions:List[ActionNode],input:Dict,llm_result:LLMResult) -> bool:
action_params = {}
action_params["_input"] = input
action_params["_memory"] = self.memory
action_params["_workspace"] = self.workspace
action_params["_llm_result"] = llm_result
action_params["_agentid"] = self.memory.agent_id
action_params["_start_at"] = datetime.now()
try:
if await self._execute_actions(actions,action_params) is False:
result_str = "execute action failed!"
except Exception as e:
logger.error(f"execute action failed! {e}")
result_str = "execute action failed!,error:" + str(e)
class AgentSelfLearning(BaseLLMProcess):
def __init__(self) -> None:
super().__init__()
async def load_from_config(self, config: dict) -> Coroutine[Any, Any, bool]:
if await super().load_from_config(config) is False:
return False
async def prepare_prompt(self) -> LLMPrompt:
prompt = LLMPrompt()
pass
async def get_inner_function_for_exec(self,func_name:str) -> AIFunction:
pass
async def post_llm_process(self,actions:List[ActionNode]) -> bool:
pass
class AgentSelfImprove(BaseLLMProcess):
def __init__(self) -> None:
super().__init__()
+42
View File
@@ -0,0 +1,42 @@
from .llm_process import BaseLLMProcess, AgentMessageProcess,AgentSelfThinking,AgentSelfLearning,AgentSelfImprove
from .llm_do_task import AgentTriageTaskList,AgentPlanTask,AgentReviewTask,AgentDo,AgentCheck
from typing import Awaitable, Callable, Coroutine, Dict, List, Any
import logging
logger = logging.getLogger(__name__)
class LLMProcessLoader:
def __init__(self) -> None:
self.loaders : Dict[str,Callable[[dict],Awaitable[BaseLLMProcess]]] = {}
return
@classmethod
def get_instance(cls)->"LLMProcessLoader":
if not hasattr(cls,"_instance"):
cls._instance = LLMProcessLoader()
return cls._instance
def register_loader(self, typename:str,loader:Callable[[dict],Awaitable[BaseLLMProcess]]):
self.loaders[typename] = loader
async def load_from_config(self,config:dict) -> BaseLLMProcess:
llm_type_name = config.get("type")
if llm_type_name:
loader = self.loaders.get(llm_type_name)
if loader:
return await loader(config)
selected_type = globals().get(llm_type_name)
if selected_type:
result : BaseLLMProcess = selected_type()
load_result = await result.load_from_config(config)
if load_result is False:
logger.warn(f"load LLMProcess {llm_type_name} from config failed! load_from_config return False")
return None
else:
return result
logger.warn(f"load LLMProcess {llm_type_name} from config failed! type not found")
return None
@@ -1,6 +1,7 @@
# pylint:disable=E0402
import logging import logging
from .agent import AIAgent,AgentPrompt from ..proto.compute_task import LLMPrompt
class AIRole: class AIRole:
def __init__(self) -> None: def __init__(self) -> None:
@@ -9,7 +10,7 @@ class AIRole:
self.role_id :str = None # $workflow_id.$sub_workflow_id.$role_name self.role_id :str = None # $workflow_id.$sub_workflow_id.$role_name
self.fullname : str = None self.fullname : str = None
self.agent_name : str = None self.agent_name : str = None
self.prompt : AgentPrompt = None self.prompt : LLMPrompt = None
self.introduce : str = None self.introduce : str = None
self.agent = None self.agent = None
self.enable_function_list : list[str] = None self.enable_function_list : list[str] = None
@@ -31,7 +32,7 @@ class AIRole:
prompt_node = config.get("prompt") prompt_node = config.get("prompt")
if prompt_node: if prompt_node:
self.prompt = AgentPrompt() self.prompt = LLMPrompt()
if self.prompt.load_from_config(prompt_node) is False: if self.prompt.load_from_config(prompt_node) is False:
logging.error("load prompt failed!") logging.error("load prompt failed!")
return False return False
@@ -41,7 +42,7 @@ class AIRole:
self.introduce = intro_node self.introduce = intro_node
history_node = config.get("history_len") history_node = config.get("history_len")
if history_node: if history_node is not None:
self.history_len = int(history_node) self.history_len = int(history_node)
if config.get("enable_function") is not None: if config.get("enable_function") is not None:
@@ -56,7 +57,7 @@ class AIRole:
def get_name(self) -> str: def get_name(self) -> str:
return self.role_name return self.role_name
def get_prompt(self) -> AgentPrompt: def get_prompt(self) -> LLMPrompt:
return self.prompt return self.prompt
class AIRoleGroup: class AIRoleGroup:
@@ -78,4 +79,3 @@ class AIRoleGroup:
def get(self,role_name:str) -> AIRole: def get(self,role_name:str) -> AIRole:
return self.roles.get(role_name) return self.roles.get(role_name)
@@ -1,3 +1,4 @@
# pylint:disable=E0402
import logging import logging
import asyncio import asyncio
import json import json
@@ -7,16 +8,19 @@ from asyncio import Queue
from typing import Optional,Tuple,List from typing import Optional,Tuple,List
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from .environment import Environment,EnvironmentEvent from ..proto.compute_task import *
from .agent_message import AgentMsg,AgentMsgStatus,FunctionItem,LLMResult from ..proto.agent_msg import *
from .agent import AgentPrompt,AgentMsg from ..proto.ai_function import *
from .agent_base import *
from .chatsession import AIChatSession from .chatsession import AIChatSession
from .role import AIRole,AIRoleGroup from .role import AIRole,AIRoleGroup
from .ai_function import AIFunction,FunctionItem
from .compute_kernel import ComputeKernel from ..frame.compute_kernel import ComputeKernel
from .compute_task import ComputeTask,ComputeTaskResult,ComputeTaskState,ComputeTaskResultCode from ..frame.bus import AIBus
from .bus import AIBus
from .workflow_env import WorkflowEnvironment from ..environment.environment import BaseEnvironment
from ..environment.workflow_env import WorkflowEnvironment
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -45,7 +49,7 @@ class Workflow:
def __init__(self) -> None: def __init__(self) -> None:
self.workflow_name : str = None self.workflow_name : str = None
self.workflow_id : str = None self.workflow_id : str = None
self.rule_prompt : AgentPrompt = None self.rule_prompt : LLMPrompt = None
self.workflow_config = None self.workflow_config = None
self.role_group : dict = None self.role_group : dict = None
self.input_filter : MessageFilter= None self.input_filter : MessageFilter= None
@@ -80,7 +84,7 @@ class Workflow:
self.db_file = self.owner_workflow.db_file self.db_file = self.owner_workflow.db_file
if config.get("prompt") is not None: if config.get("prompt") is not None:
self.rule_prompt = AgentPrompt() self.rule_prompt = LLMPrompt()
if self.rule_prompt.load_from_config(config.get("prompt")) is False: if self.rule_prompt.load_from_config(config.get("prompt")) is False:
logger.error("Workflow load prompt failed") logger.error("Workflow load prompt failed")
return False return False
@@ -238,77 +242,6 @@ class Workflow:
error_resp = msg.create_error_resp(err_str) error_resp = msg.create_error_resp(err_str)
return error_resp return error_resp
@classmethod
def prase_llm_result(cls,llm_result_str:str)->LLMResult:
r = LLMResult()
if llm_result_str is None:
r.state = "ignore"
return r
if llm_result_str == "ignore":
r.state = "ignore"
return r
lines = llm_result_str.splitlines()
is_need_wait = False
def check_args(func_item:FunctionItem):
match func_name:
case "send_msg":# sendmsg($target_id,$msg_content)
if len(func_args) != 1:
logger.error(f"parse sendmsg failed! {func_call}")
return False
new_msg = AgentMsg()
target_id = func_item.args[0]
msg_content = func_item.body
new_msg.set("_",target_id,msg_content)
r.send_msgs.append(new_msg)
is_need_wait = True
case "post_msg":# postmsg($target_id,$msg_content)
if len(func_args) != 1:
logger.error(f"parse postmsg failed! {func_call}")
return False
new_msg = AgentMsg()
target_id = func_item.args[0]
msg_content = func_item.body
new_msg.set("_",target_id,msg_content)
r.post_msgs.append(new_msg)
case "call":# call($func_name,$args_str)
r.calls.append(func_item)
is_need_wait = True
return True
case "post_call": # post_call($func_name,$args_str)
r.post_calls.append(func_item)
return True
current_func : FunctionItem = None
for line in lines:
if line.startswith("##/"):
if current_func:
if check_args(current_func) is False:
r.resp += current_func.dumps()
func_name,func_args = AgentMsg.parse_function_call(line[3:])
current_func = FunctionItem(func_name,func_args)
else:
if current_func:
current_func.append_body(line + "\n")
else:
r.resp += line + "\n"
if current_func:
if check_args(current_func) is False:
r.resp += current_func.dumps()
if len(r.send_msgs) > 0 or len(r.calls) > 0:
r.state = "waiting"
else:
r.state = "reponsed"
return r
async def role_post_msg(self,msg:AgentMsg,the_role:AIRole,workflow_chat_session:AIChatSession): async def role_post_msg(self,msg:AgentMsg,the_role:AIRole,workflow_chat_session:AIChatSession):
msg.sender = the_role.get_role_id() msg.sender = the_role.get_role_id()
@@ -347,7 +280,7 @@ class Workflow:
logger.info(f"{msg.sender} post message {msg.msg_id} to AIBus: {msg.target}") logger.info(f"{msg.sender} post message {msg.msg_id} to AIBus: {msg.target}")
return await self.get_bus().send_message(msg) return await self.get_bus().send_message(msg)
async def role_call(self,func_item:FunctionItem,the_role:AIRole): async def role_call(self,func_item:ActionNode,the_role:AIRole):
logger.info(f"{the_role.role_id} call {func_item.name} ") logger.info(f"{the_role.role_id} call {func_item.name} ")
arguments = func_item.args arguments = func_item.args
@@ -358,11 +291,11 @@ class Workflow:
result_str:str = await func_node.execute(**arguments) result_str:str = await func_node.execute(**arguments)
return result_str return result_str
async def role_post_call(self,func_item:FunctionItem,the_role:AIRole): async def role_post_call(self,func_item:ActionNode,the_role:AIRole):
logger.info(f"{the_role.role_id} post call {func_item.name} ") logger.info(f"{the_role.role_id} post call {func_item.name} ")
return await self.role_call(func_item,the_role) return await self.role_call(func_item,the_role)
def _format_msg_by_env_value(self,prompt:AgentPrompt): def _format_msg_by_env_value(self,prompt:LLMPrompt):
if self.workflow_env is None: if self.workflow_env is None:
return return
@@ -377,11 +310,11 @@ class Workflow:
result_func = [] result_func = []
for inner_func in all_inner_function: for inner_func in all_inner_function:
func_name = inner_func.get_name() func_name = inner_func.get_id()
if the_role.enable_function_list is not None: if the_role.enable_function_list is not None:
if len(the_role.enable_function_list) > 0: if len(the_role.enable_function_list) > 0:
if func_name not in the_role.enable_function_list: if func_name not in the_role.enable_function_list:
logger.debug(f"agent {self.agent_id} ignore inner func:{func_name}") logger.debug(f"agent {the_role.agent.agent_id} ignore inner func:{func_name}")
continue continue
else: else:
continue continue
@@ -394,8 +327,7 @@ class Workflow:
return result_func return result_func
return None return None
async def _role_execute_func(self,the_role:AIRole,inenr_func_call_node:dict,prompt:AgentPrompt,org_msg:AgentMsg,stack_limit = 5) -> [str,int]: async def _role_execute_func(self,the_role:AIRole,inenr_func_call_node:dict,prompt:LLMPrompt,org_msg:AgentMsg,stack_limit = 5) -> [str,int]:
from .compute_kernel import ComputeKernel
func_name = inenr_func_call_node.get("name") func_name = inenr_func_call_node.get("name")
arguments = json.loads(inenr_func_call_node.get("arguments")) arguments = json.loads(inenr_func_call_node.get("arguments"))
@@ -410,6 +342,7 @@ class Workflow:
except Exception as e: except Exception as e:
result_str = f"execute {func_name} error:{str(e)}" result_str = f"execute {func_name} error:{str(e)}"
logger.error(f"llm execute inner func:{func_name} error:{e}") logger.error(f"llm execute inner func:{func_name} error:{e}")
logger.exception(e)
inner_functions = self._get_inner_functions(the_role) inner_functions = self._get_inner_functions(the_role)
@@ -440,8 +373,7 @@ class Workflow:
async def role_process_msg(self,msg:AgentMsg,the_role:AIRole,workflow_chat_session:AIChatSession) -> AgentMsg: async def role_process_msg(self,msg:AgentMsg,the_role:AIRole,workflow_chat_session:AIChatSession) -> AgentMsg:
msg.target = the_role.get_role_id() msg.target = the_role.get_role_id()
prompt = LLMPrompt()
prompt = AgentPrompt()
prompt.append(the_role.agent.agent_prompt) prompt.append(the_role.agent.agent_prompt)
prompt.append(self.get_workflow_rule_prompt()) prompt.append(self.get_workflow_rule_prompt())
prompt.append(the_role.get_prompt()) prompt.append(the_role.get_prompt())
@@ -451,7 +383,7 @@ class Workflow:
#support group chat, user content include sender name! #support group chat, user content include sender name!
prompt.append(await self._get_prompt_from_session(the_role,workflow_chat_session)) prompt.append(await self._get_prompt_from_session(the_role,workflow_chat_session))
msg_prompt = AgentPrompt() msg_prompt = LLMPrompt()
msg_prompt.messages = [{"role":"user","content":f"user name is {msg.sender}, his question is :{msg.body}"}] msg_prompt.messages = [{"role":"user","content":f"user name is {msg.sender}, his question is :{msg.body}"}]
prompt.append(msg_prompt) prompt.append(msg_prompt)
@@ -480,7 +412,7 @@ class Workflow:
error_resp = msg.create_error_resp(result_str) error_resp = msg.create_error_resp(result_str)
return error_resp return error_resp
result : LLMResult = Workflow.prase_llm_result(result_str) result : LLMResult = LLMResult.from_str(result_str)
for postmsg in result.post_msgs: for postmsg in result.post_msgs:
postmsg.prev_msg_id = msg.get_msg_id() postmsg.prev_msg_id = msg.get_msg_id()
# might be craete a new msg.topic for this postmsg # might be craete a new msg.topic for this postmsg
@@ -530,20 +462,20 @@ class Workflow:
# message will be saved in role.process_message # message will be saved in role.process_message
pass pass
this_llm_resp_prompt = AgentPrompt() this_llm_resp_prompt = LLMPrompt()
this_llm_resp_prompt.messages = [{"role":"assistant","content":result_str}] this_llm_resp_prompt.messages = [{"role":"assistant","content":result_str}]
prompt.append(this_llm_resp_prompt) prompt.append(this_llm_resp_prompt)
result_prompt = AgentPrompt() result_prompt = LLMPrompt()
result_prompt.messages = [{"role":"user","content":result_prompt_str}] result_prompt.messages = [{"role":"user","content":result_prompt_str}]
prompt.append(result_prompt) prompt.append(result_prompt)
return await _do_process_msg() return await _do_process_msg()
return await _do_process_msg() return await _do_process_msg()
async def _get_prompt_from_session(self,the_role:AIRole,chatsession:AIChatSession) -> AgentPrompt: async def _get_prompt_from_session(self,the_role:AIRole,chatsession:AIChatSession) -> LLMPrompt:
messages = chatsession.read_history(the_role.history_len) # read last 10 message messages = chatsession.read_history(the_role.history_len) # read last 10 message
result_prompt = AgentPrompt() result_prompt = LLMPrompt()
for msg in reversed(messages): for msg in reversed(messages):
if msg.sender == the_role.role_id: if msg.sender == the_role.role_id:
@@ -553,21 +485,21 @@ class Workflow:
return result_prompt return result_prompt
def _get_knowlege_prompt(self,role_name:str) -> AgentPrompt: def _get_knowlege_prompt(self,role_name:str) -> LLMPrompt:
pass pass
def get_workflow_rule_prompt(self) -> AgentPrompt: def get_workflow_rule_prompt(self) -> LLMPrompt:
return self.rule_prompt return self.rule_prompt
def _env_event_to_msg(self,env_event:EnvironmentEvent) -> AgentMsg: # def _env_event_to_msg(self,env_event:EnvironmentEvent) -> AgentMsg:
# pass
def get_inner_environment(self,env_id:str) -> BaseEnvironment:
pass pass
def get_inner_environment(self,env_id:str) -> Environment: def connect_to_environment(self,the_env:BaseEnvironment,conn_info:dict) -> None:
pass
def connect_to_environment(self,the_env:Environment,conn_info:dict) -> None:
if the_env is not None: if the_env is not None:
self.workflow_env.add_owner_env(the_env) self.workflow_env.add_env(the_env)
#for event2msg in conn_info: #for event2msg in conn_info:
# for k,v in event2msg: # for k,v in event2msg:
+787
View File
@@ -0,0 +1,787 @@
# pylint:disable=E0402
from ast import Dict
import json
import sqlite3
import os
import glob
import time
from typing import List, Optional
import aiofiles
from ..proto.agent_msg import AgentMsg
from ..proto.ai_function import AIFunction, ParameterDefine,SimpleAIFunction,ActionNode,SimpleAIAction
from ..proto.agent_task import AgentTask, AgentTaskState,AgentTodo,AgentWorkLog,AgentTaskManager
from ..storage.storage import AIStorage
from ..frame.bus import AIBus
from .llm_context import GlobaToolsLibrary
import logging
logger = logging.getLogger(__name__)
class LocalAgentTaskManger(AgentTaskManager):
def __init__(self, owner_id):
super().__init__()
self.root_path = f"{AIStorage.get_instance().get_myai_dir()}/agent_data/{owner_id}/workspace/"
#self.root_path = os.path.join(workspace, list_type)
if not os.path.exists(self.root_path):
os.makedirs(self.root_path)
self.db_path = os.path.join(self.root_path, "tasklist.db")
self.conn = None
try:
self.conn = sqlite3.connect(self.db_path)
except Exception as e:
logger.error("Error occurred while connecting to database: %s", e)
return None
cursor = self.conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS obj_list (
id TEXT,
path TEXT
)
''')
self.conn.commit()
def _get_obj_path(self,objid:str) -> str:
cursor = self.conn.cursor()
cursor.execute('''
SELECT path FROM obj_list WHERE id = ?
''',(objid,))
row = cursor.fetchone()
if row:
return row[0]
else:
return None
def _save_obj_path(self,objid:str,path:str):
cursor = self.conn.cursor()
cursor.execute('''
INSERT INTO obj_list (id,path) VALUES (?,?)
''',(objid,path))
self.conn.commit()
async def create_task(self,task:AgentTask,parent_id:str = None) -> str:
try:
#perfix = task.task_id[-5]
if parent_id:
parent_path = self._get_obj_path(parent_id)
task_path = f"{parent_path}/{task.title}"
else:
task_path = f"{task.title}"
dir_path = f"{self.root_path}/{task_path}"
os.makedirs(dir_path)
detail_path = f"{dir_path}/detail"
if task.task_path is None:
task.task_path = task_path
self._save_obj_path(task.task_id,task_path)
logger.info("create_task at %s",detail_path)
async with aiofiles.open(detail_path, mode='w', encoding="utf-8") as f:
await f.write(json.dumps(task.to_dict(),ensure_ascii=False))
return "create task ok"
except Exception as e:
logger.error("create_task failed:%s",e)
return str(e)
async def set_todos(self,owner_task_id:str,todos:List[Dict]):
owner_task_path = self._get_obj_path(owner_task_id)
if owner_task_path is None:
return f"owner task {owner_task_id} not found"
try:
directory = f"{self.root_path}/{owner_task_path}"
file_extension = "*.todo"
pattern = os.path.join(directory, file_extension)
files = glob.glob(pattern)
for file in files:
os.remove(file)
logger.info(f"Deleted {file}")
except Exception as e:
logger.error("set_todos deleted todos failed:%s",e)
try:
step_order = 0
for todo in todos:
todo_obj = AgentTodo.from_dict(todo)
todo_obj.step_order = step_order
todo_obj.owner_taskid = owner_task_id
todo_path = f"{self.root_path}/{owner_task_path}/#{step_order} {todo_obj.title}.todo"
self._save_obj_path(todo_obj.todo_id,todo_path)
async with aiofiles.open(todo_path, mode='w', encoding="utf-8") as f:
await f.write(json.dumps(todo_obj.to_dict(),ensure_ascii=False))
logger.info("create_todos at %s OK!",todo_path)
step_order += 1
except Exception as e:
logger.error("create_todos failed:%s",e)
return str(e)
return None
async def append_worklog(self,task:AgentTask,log:AgentWorkLog):
worklog = f"{self.root_path}/{task.task_path}/.worklog"
async with aiofiles.open(worklog, mode='w+', encoding="utf-8") as f:
content = await f.read()
if len(content) > 0:
json_obj = json.loads(content)
else:
json_obj = {}
logs = json_obj.get("logs")
if logs is None:
logs = []
logs.append(log.to_dict())
json_obj["logs"] = logs
await f.write(json.dumps(json_obj,ensure_ascii=False))
async def get_worklog(self,obj_id:str)->List[AgentWorkLog]:
obj_path = self._get_obj_path(obj_id)
if obj_path is None:
return []
if obj_path.endswith(".todo"):
dir_path = os.path.dirname(obj_path)
worklog_path = f"{self.root_path}/{dir_path}/.worklog"
else:
worklog_path = f"{self.root_path}/{obj_path}/.worklog"
async with aiofiles.open(worklog_path, mode='r', encoding="utf-8") as f:
content = await f.read()
if len(content) > 0:
json_obj = json.loads(content)
else:
json_obj = {}
logs = json_obj.get("logs")
return logs
async def get_task(self,task_id:str) -> AgentTask:
task_path = self._get_obj_path(task_id)
if task_path is None:
logger.error("get_task:%s,not found!",task_id)
return None
return await self.get_task_by_path(task_path)
async def _get_task_by_fullpath(self,task_fullpath) -> AgentTask:
detail_path = f"{task_fullpath}/detail"
try:
with open(detail_path, mode='r', encoding="utf-8") as f:
task_dict = json.load(f)
result_task:AgentTask = AgentTask.from_dict(task_dict)
if result_task:
relative_path = os.path.relpath(task_fullpath, self.root_path)
result_task.task_path = relative_path
else:
logger.error("_get_task_by_fullpath:%s,parse failed!",detail_path)
return result_task
except Exception as e:
logger.error("_get_task_by_fullpath:%s,failed:%s",task_fullpath,e)
return None
async def get_task_by_path(self,task_path:str) -> AgentTask:
full_path = f"{self.root_path}/{task_path}"
return await self._get_task_by_fullpath(full_path)
async def get_todo(self,todo_id:str) -> AgentTodo:
todo_path = self._get_obj_path(todo_id)
if todo_path is None:
logger.error("get_todo:%s,not found!",todo_id)
return None
try:
with open(todo_path, mode='r', encoding="utf-8") as f:
todo_dict = json.load(f)
result_todo:AgentTodo = AgentTodo.from_dict(todo_dict)
if result_todo:
result_todo.todo_path = todo_path
else:
logger.error("get_todo:%s,parse failed!",todo_path)
return result_todo
except Exception as e:
logger.error("get_todo:%s,failed:%s",todo_path,e)
return None
async def get_sub_tasks(self,task_id:str) -> List[AgentTask]:
task_path = self._get_obj_path(task_id)
if task_path is None:
return []
task_path = f"{self.root_path}/{task_path}"
sub_tasks = []
for sub_item in os.listdir(task_path):
if sub_item.startswith("."):
continue
if sub_item == "workspace":
continue
full_path = os.path.join(task_path, sub_item)
if os.path.isdir(full_path):
sub_task = await self.get_task_by_path(f"{task_path}/{sub_item}")
if sub_task:
sub_tasks.append(sub_task)
return sub_tasks
async def get_sub_todos(self,task_id:str) -> List[AgentTodo]:
task_path = self._get_obj_path(task_id)
if task_path is None:
return []
task_path = f"{self.root_path}/{task_path}"
sub_todos = []
for sub_item in os.listdir(task_path):
if sub_item.startswith("."):
continue
if sub_item == "workspace":
continue
if sub_item == "details":
continue
full_path = os.path.join(task_path, sub_item)
if os.path.isfile(full_path) and sub_item.endswith(".todo"):
sub_todo = await self.get_todo_by_path(full_path)
if sub_todo:
sub_todos.append(sub_todo)
return sub_todos
async def get_todo_by_path(self,todo_path:str) -> AgentTodo:
async with aiofiles.open(todo_path, mode='r', encoding="utf-8") as f:
s = await f.read()
todo_dict = json.loads(s)
result_todo:AgentTodo = AgentTodo.from_dict(todo_dict)
if result_todo:
result_todo.todo_path = todo_path
else:
logger.error("get_todo_by_path:%s,parse failed!",todo_path)
return result_todo
#async def get_task_depends(self,task_id:str) -> List[AgentTask]:
# pass
async def list_task(self,filter:Optional[dict] = None ) -> List[AgentTask]:
directory_path = self.root_path
result_list:List[AgentTask] = []
special_state = None
if filter:
special_state = filter.get("state")
#agent_id = filter.get("agent_id")
for entry in os.scandir(directory_path):
if not entry.is_dir():
continue
if entry.name.startswith("."):
continue
if entry.name == "workspace":
continue
task_item = await self._get_task_by_fullpath(entry.path)
if task_item:
if filter is None:
if task_item.is_finish():
continue
if special_state:
if task_item.state != special_state:
continue
result_list.append(task_item)
return result_list
async def update_task(self,task:AgentTask):
detail_path = f"{self.root_path}/{task.task_path}/detail"
try:
new_task_content = json.dumps(task.to_dict(),ensure_ascii=False)
async with aiofiles.open(detail_path, mode='w', encoding="utf-8") as f:
await f.write(new_task_content)
except Exception as e:
logger.error("update_task failed:%s",e)
return str(e)
return None
async def update_todo(self,todo:AgentTodo):
todo_path = self._get_obj_path(todo.todo_id)
if todo_path is None:
return f"todo {todo.todo_id} not found"
try:
new_todo_content = json.dumps(todo.to_dict(),ensure_ascii=False)
async with aiofiles.open(todo_path, mode='w', encoding="utf-8") as f:
await f.write(new_todo_content)
except Exception as e:
logger.error("update_todo failed:%s",e)
return str(e)
return None
#async def update_task_state(self,task_id,state:str):
# pass
#async def update_todo_state(self,task_id,state:str):
# pass
#todo共享其所在task的文件夹
# if task_id is none, means root folder in workspace
def _get_taskfile_path(self,task_id:str,path:str)->str:
root_path = self.root_path
if task_id is None:
root_path = f"{root_path}/workspace"
else:
task_path = self._get_obj_path(task_id)
if task_path is None:
return None
root_path = f"{task_path}/wrorkspace"
file_path = f"{root_path}/{path}"
return file_path
async def read_task_file(self,task_id:str,path:str)->str:
file_path = self._get_taskfile_path(task_id,path)
if not os.path.exists(file_path):
return None
try:
async with aiofiles.open(file_path, mode='r', encoding="utf-8") as f:
content = await f.read()
return content
except Exception as e:
logger.error("read_task_file failed:%s",e)
return None
async def write_task_file(self,task_id:str,path:str,content:str):
file_path = self._get_taskfile_path(task_id,path)
# write file
try:
dir_name = os.path.dirname(file_path)
os.makedirs(dir_name)
async with aiofiles.open(file_path, mode='w', encoding="utf-8") as f:
await f.write(content)
except Exception as e:
logger.error("write_task_file failed:%s",e)
return str(e)
async def append_task_file(self,task_id:str,path:str,content:str):
file_path = self._get_taskfile_path(task_id,path)
# append file
try:
async with aiofiles.open(file_path, mode='a', encoding="utf-8") as f:
await f.write(content)
except Exception as e:
logger.error("append_task_file failed:%s",e)
return str(e)
async def list_task_dir(self,task_id:str,path:str) -> List[str]:
dir_path = self._get_taskfile_path(task_id,path)
if not os.path.exists(dir_path):
return None
try:
result_node = os.listdir(dir_path)
result = []
for name in result_node:
if name.startswith("."):
continue
result.append(name)
return result
except Exception as e:
logger.error("list_task_dir failed:%s",e)
return None
async def remove_task_file(self,task_id:str,path:str):
file_path = self._get_taskfile_path(task_id,path)
try:
os.remove(file_path)
except Exception as e:
logger.error("remove_task_file failed:%s",e)
return str(e)
return None
class AgentWorkspace:
def __init__(self,owner_id:str) -> None:
self.owner_id : str = owner_id
self.task_mgr : AgentTaskManager = LocalAgentTaskManger(owner_id)
@staticmethod
def register_ai_functions():
async def post_message(parameters):
_agent_id = parameters.get("_agentid")
if _agent_id is None:
return "_agentid not found"
target = parameters.get("target")
if target is None:
return "target not found"
message = parameters.get("message")
if message is None:
return "message not found"
topic = parameters.get("topic")
msg = AgentMsg()
msg.sender = _agent_id
msg.body = message
msg.topic = topic
msg.target = target
msg.create_time = time.time()
is_post_ok = await AIBus.get_default_bus().post_message(msg)
if is_post_ok:
return "post message ok!"
else:
return f"post message to {target} failed!"
parameters = ParameterDefine.create_parameters({
"target": {"type": "string", "description": "target agent/contact fullname or telephone or email"},
"topic": {"type": "string", "description": "optional, message topic"},
"message": {"type": "string", "description": "message content"},
})
post_message_action = SimpleAIFunction(
"post_message",
"Post a message to target agent/contact",
post_message,
parameters,
)
GlobaToolsLibrary.get_instance().register_tool_function(post_message_action)
async def send_message(parameters):
_agent_id = parameters.get("_agentid")
if _agent_id is None:
return "_agentid not found"
target = parameters.get("target")
if target is None:
return "target not found"
message = parameters.get("message")
if message is None:
return "message not found"
topic = parameters.get("topic")
msg = AgentMsg()
msg.sender = _agent_id
msg.body = message
msg.topic = topic
msg.target = target
msg.create_time = time.time()
resp = await AIBus.get_default_bus().send_message(msg)
if resp:
return f"resp is : {resp.body}"
else:
return f"send message to {target} failed!"
parameters = ParameterDefine.create_parameters({
"target": {"type": "string", "description": "target agent/contact id"},
"topic": {"type": "string", "description": "optional, message topic"},
"message": {"type": "string", "description": "message content"},
})
send_message_action = SimpleAIFunction(
"send_message",
"send a message to target agent/contact, and wait for reply",
send_message,
parameters,
)
GlobaToolsLibrary.get_instance().register_tool_function(send_message_action)
async def create_task(params):
_workspace = params.get("_workspace")
_agent_id = params.get("_agentid")
if _workspace is None:
return "_workspace not found"
if params.get("creator") is None:
params["creator"] = _agent_id
taskObj = AgentTask.create_by_dict(params)
parent_id = params.get("parent")
return await _workspace.task_mgr.create_task(taskObj,parent_id)
parameters = ParameterDefine.create_parameters({
"title" : {"type": "string", "description": "task title,Simple and clear, try to include the task \ Related personnel \ place \ key conditions \ time element involved in the event"},
"detail" : {"type": "string", "description": "task detail(simple task can not be filled)"},
"priority" : {"type": "int", "description": "task priority from 1-10"},
#"due_date": {"type": "isoformat time string", "description": "optional,confirm task due date"},
#"expiration_time": {"type": "isoformat time string", "description": "optional,confirm task expiration time"},
"parent": {"type": "string", "description": "optional,parent task id"},
})
create_task_action = SimpleAIFunction(
"agent.workspace.create_task",
"Create a task",
create_task,
parameters,
)
GlobaToolsLibrary.get_instance().register_tool_function(create_task_action)
async def cancel_task(parameters):
_workspace = parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
task_id = parameters.get("task_id")
task : AgentTask = await _workspace.task_mgr.get_task(task_id)
if task is None:
return f"task {task_id} not found"
task.state = AgentTaskState.TASK_STATE_CANCEL
await _workspace.task_mgr.update_task(task)
return "canncel task ok"
parameters = ParameterDefine.create_parameters({
"task_id": {"type": "string", "description": "task id which want to cancel"},
})
cancel_task_action = SimpleAIFunction(
"agent.workspace.cancel_task",
"Cancel this task",
cancel_task,
parameters
)
GlobaToolsLibrary.get_instance().register_tool_function(cancel_task_action)
async def confirm_task(parameters):
_workspace = parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
task_id = parameters.get("task_id")
task : AgentTask = await _workspace.task_mgr.get_task(task_id)
if task is None:
return f"task {task_id} not found"
if parameters.get("priority"):
task.priority = parameters.get("priority")
if parameters.get("next_attention_time"):
task.next_attention_time = parameters.get("next_attention_time")
if parameters.get("expiration_time"):
task.expiration_time = parameters.get("expiration_time")
if parameters.get("due_date"):
task.due_date = parameters.get("due_date")
task.state = AgentTaskState.TASK_STATE_CONFIRMED
await _workspace.task_mgr.update_task(task)
return "confirm task ok"
parameters = ParameterDefine.create_parameters({
"task_id": {"type": "string", "description": "task id which want to confirm"},
"next_attention_time": {"type": "isoformat time string", "description": "optional,confirm task next attention time"},
"expiration_time": {"type": "isoformat time string", "description": "optional,confirm task expiration time"},
#"due_date": {"type": "isoformat time string", "description": "optional,confirm task due date"},
"priority": {"type": "int", "description": "optional,task priority from 1-10"},
})
confirm_task_action = SimpleAIFunction(
"agent.workspace.confirm_task",
"After understanding the content of the task, the importance of the importance of the task, the priority, the deadline, etc.",
confirm_task,
parameters
)
GlobaToolsLibrary.get_instance().register_tool_function(confirm_task_action)
async def list_task(parameters):
_workspace = parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
all_task = await _workspace.task_mgr.list_task(None)
if all_task:
return json.dumps([task.to_dict() for task in all_task],ensure_ascii=False)
else :
return "no task"
list_task_ai_function = SimpleAIFunction("agent.workspace.list_task",
"list all tasks in json format",
list_task,{})
GlobaToolsLibrary.get_instance().register_tool_function(list_task_ai_function)
async def update_task(parameters):
_workspace : AgentWorkspace= parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
task_id = parameters.get("task_id")
task:AgentTask = await _workspace.task_mgr.get_task(task_id)
if task is None:
return f"task {task_id} not found"
if parameters.get("title"):
task.title = parameters.get("title")
if parameters.get("detail"):
task.detail = parameters.get("detail")
if parameters.get("priority"):
task.priority = parameters.get("priority")
if parameters.get("new_state"):
task.state = AgentTaskState.from_str(parameters.get("new_state"))
if parameters.get("next_attention_time"):
task.next_attention_time = parameters.get("next_attention_time")
if parameters.get("due_date"):
task.due_date = parameters.get("due_date")
if parameters.get("expiration_time"):
task.expiration_time = parameters.get("expiration_time")
await _workspace.task_mgr.update_task(task)
return "update task ok"
parameters = ParameterDefine.create_parameters({
"task_id": {"type": "string", "description": "task id which want to update"},
"new_state": {"type": "string", "description": "optional,new task state: cancel or done"},
"next_attention_time": {"type": "isoformat time string", "description": "optional,update task next attention time"},
"expiration_time": {"type": "isoformat time string", "description": "optional,update task expiration time"},
"priority": {"type": "int", "description": "optional,task priority from 1-10"},
"title": {"type": "string", "description": "optional, new task title"},
"detail": {"type": "string", "description": "optional, new task detail(simple task can not be filled)"},
#"due_date": {"type": "string", "description": "optional,new task due date"},
})
update_task_ai_function = SimpleAIFunction("agent.workspace.update_task",
"update task to new state",
update_task,parameters)
GlobaToolsLibrary.get_instance().register_tool_function(update_task_ai_function)
async def set_todos(parameters):
_workspace : AgentWorkspace= parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
task_id = parameters.get("task_id")
task:AgentTask = await _workspace.task_mgr.get_task(task_id)
if task is None:
return f"task {task_id} not found"
todos = parameters.get("todos")
if todos is None:
return "todos not found"
await _workspace.task_mgr.set_todos(task_id,todos)
return "set todos ok"
todo_demo = """
[
{
"title": "todo1",
"detail": "todo1 detail",
"tags": "tag1,tag2",
"due_date": "2021-01-01",
"priority": 1
},
]
"""
parameters = ParameterDefine.create_parameters({
"task_id": {"type": "string", "description": "task id which want to set todos"},
"todos": {"type": "list", "description": f"List of todo, todo is a dict like {todo_demo}"},
})
set_todos_ai_function = SimpleAIFunction("agent.workspace.set_todos",
"set todos for task",
set_todos,parameters)
GlobaToolsLibrary.get_instance().register_tool_function(set_todos_ai_function)
async def update_todo(parameters):
_workspace : AgentWorkspace= parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
todo_id = parameters.get("todo_id")
todo : AgentTodo = await _workspace.task_mgr.get_todo(todo_id)
if todo is None:
return f"todo {todo_id} not found"
parameters = ParameterDefine.create_parameters({
"todo_id": {"type": "string", "description": "todo id which want to update"},
"new_state": {"type": "string", "description": "optional,new todo state: execute_ok , execute_failed, done or check_failed"},
})
update_todo_ai_function = SimpleAIFunction("agent.workspace.update_todo",
"update todo to new state",
update_todo,parameters)
GlobaToolsLibrary.get_instance().register_tool_function(update_todo_ai_function)
# write file
async def write_task_file(parameters):
_workspace : AgentWorkspace= parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
task_id = parameters.get("task_id")
path = parameters.get("filename")
content = parameters.get("content")
await _workspace.task_mgr.write_task_file(task_id,path,content)
return "write task file ok"
parameters = ParameterDefine.create_parameters({
"filename": {"type": "string", "description": "filename"},
"content": {"type": "string", "description": "file content"},
})
write_task_file_ai_function = SimpleAIFunction("agent.workspace.write_file",
"write file for task",
write_task_file,parameters)
GlobaToolsLibrary.get_instance().register_tool_function(write_task_file_ai_function)
# append file
async def append_task_file(parameters):
_workspace : AgentWorkspace= parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
task_id = parameters.get("task_id")
path = parameters.get("filename")
content = parameters.get("content")
await _workspace.task_mgr.append_task_file(task_id,path,content)
return "append task file ok"
parameters = ParameterDefine.create_parameters({
"filename": {"type": "string", "description": "filename"},
"content": {"type": "string", "description": "file content"},
})
append_task_file_ai_function = SimpleAIFunction("agent.workspace.append_file",
"append file for task",
append_task_file,parameters)
GlobaToolsLibrary.get_instance().register_tool_function(append_task_file_ai_function)
# read file
async def read_task_file(parameters):
_workspace : AgentWorkspace= parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
task_id = parameters.get("task_id")
path = parameters.get("filename")
content = await _workspace.task_mgr.read_task_file(task_id,path)
return content
parameters = ParameterDefine.create_parameters({
"filename": {"type": "string", "description": "filename"},
})
read_task_file_ai_function = SimpleAIFunction("agent.workspace.read_file",
"read file for task",
read_task_file,parameters)
GlobaToolsLibrary.get_instance().register_tool_function(read_task_file_ai_function)
# list dir
async def list_task_dir(parameters):
_workspace : AgentWorkspace= parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
task_id = parameters.get("task_id")
path = parameters.get("path")
content = await _workspace.task_mgr.list_task_dir(task_id,path)
return content
parameters = ParameterDefine.create_parameters({
"path": {"type": "string", "description": "The relative path of the dir"},
})
list_task_dir_ai_function = SimpleAIFunction("agent.workspace.list_dir",
"list dir in task workspace",
list_task_dir,parameters)
GlobaToolsLibrary.get_instance().register_tool_function(list_task_dir_ai_function)
# remove file
async def remove_task_file(parameters):
_workspace : AgentWorkspace= parameters.get("_workspace")
if _workspace is None:
return "_workspace not found"
task_id = parameters.get("task_id")
path = parameters.get("filename")
content = await _workspace.task_mgr.remove_task_file(task_id,path)
return content
parameters = ParameterDefine.create_parameters({
"filename": {"type": "string", "description": "filename"},
})
remove_task_file_ai_function = SimpleAIFunction("agent.workspace.remove_file",
"remove file for task",
remove_task_file,parameters)
GlobaToolsLibrary.get_instance().register_tool_function(remove_task_file_ai_function)
+57
View File
@@ -0,0 +1,57 @@
# pylint:disable=E0402
import logging
from typing import Dict
from ..proto.ai_function import *
from ..agent.llm_context import GlobaToolsLibrary
from ..frame.compute_kernel import ComputeKernel
logger = logging.getLogger(__name__)
class AsrFunction(AIFunction):
def __init__(self):
self.func_id = "aigc.voice_to_text"
self.description = "Voice recognition, convert the voice into text"
self.parameters = ParameterDefine.create_parameters({
"audio_file": {"type": "string", "description": "Audio file path"},
"model": {"type": "string", "description": "Recognition model", "enum": ["openai-whisper"]},
"prompt": {"type": "string", "description": "Prompt statement, can be None"},
"response_format": {"type": "string", "description": "Return format", "enum": ["text", "json", "srt", "verbose_json", "vtt"]},
})
def register_function(self):
GlobaToolsLibrary.get_instance().register_tool_function(self)
def get_id(self) -> str:
return self.func_id
def get_description(self) -> str:
return self.description
def get_parameters(self) -> Dict:
return self.parameters
async def execute(self, **kwargs) -> str:
logger.info(f"execute asr function: {kwargs}")
audio_file = kwargs.get("audio_file")
model = kwargs.get("model")
prompt = kwargs.get("prompt")
response_format = kwargs.get("response_format")
if response_format is None:
response_format = "text"
result = await ComputeKernel.get_instance().do_speech_to_text(audio_file, model, prompt, response_format)
if result is not None:
return f"exec speech_to_text Ok. {response_format} is\n```\n{result.result_str}\n```"
else:
return "exec speech_to_text failed"
def is_local(self) -> bool:
return True
def is_in_zone(self) -> bool:
return True
def is_ready_only(self) -> bool:
return False
+460
View File
@@ -0,0 +1,460 @@
# pylint:disable=E0402
import logging
import os
import pathlib
import shutil
import subprocess
import sys
import re
import time
import ast
from concurrent.futures import ThreadPoolExecutor
from hashlib import md5
from typing import Optional, Union, List, Tuple
from generic_escape import GenericEscape
from ..storage.storage import AIStorage
try:
import docker
except ImportError:
docker = None
CODE_BLOCK_PATTERN = r"```[ \t]*(\w+)?[ \t]*\r?\n(.*?)\r?\n[ \t]*```"
UNKNOWN = "unknown"
TIMEOUT_MSG = "Timeout"
DEFAULT_TIMEOUT = 600
WIN32 = sys.platform == "win32"
PATH_SEPARATOR = WIN32 and "\\" or "/"
logger = logging.getLogger(__name__)
BUILT_IN_MODULES = set(
[
"sys",
"os",
"math",
"random",
"datetime",
"json",
"re",
"subprocess",
"time",
"threading",
"logging",
"collections",
"itertools",
"functools",
"operator",
"pathlib",
"shutil",
"tempfile",
"pickle",
"io",
"argparse",
"typing",
"unittest",
"contextlib",
"abc",
"heapq",
"bisect",
"copy",
"decimal",
"fractions",
"hashlib",
"secrets",
"statistics",
"difflib",
"doctest",
"enum",
"inspect",
"traceback",
"weakref",
"gc",
"mmap",
"msvcrt",
"winreg",
"array",
"audioop",
"binascii",
"cProfile",
"concurrent.futures",
"configparser",
"csv",
"ctypes",
"dateutil",
"dis",
"fnmatch",
"getopt",
"glob",
"gzip",
"pdb",
"pprint",
"profile",
"pstats",
"queue",
"socket",
"sqlite3",
"ssl",
"struct",
"tarfile",
"telnetlib",
"timeit",
"tokenize",
"uuid",
"xml",
"zipfile",
"zlib",
]
)
def get_imports(code: str) -> List[str]:
root = ast.parse(code)
imports = []
for node in ast.iter_child_nodes(root):
if isinstance(node, ast.Import):
module_names = [alias.name for alias in node.names]
elif isinstance(node, ast.ImportFrom):
module_names = [node.module]
else:
continue
for name in module_names:
# Exclude built-in modules
if name not in BUILT_IN_MODULES:
imports.append(name)
return imports
def write_requirements(code: str, requirements_filepath: str):
imports = get_imports(code)
with open(requirements_filepath, "w") as file:
for module in imports:
file.write(module + "\n")
def _cmd(lang):
if lang.startswith("python") or lang in ["bash", "sh", "powershell"]:
return lang
if lang in ["shell"]:
return "sh"
if lang in ["ps1"]:
return "powershell"
raise NotImplementedError(f"{lang} not recognized in code execution")
def create_runner(code: str, timeout: int = 30) -> str:
"""
Create a Python script that runs the code and prints the output
"""
code = GenericEscape().escape(code)
# Create a runner script
runner = f"""
import os
import subprocess
my_env = os.environ.copy()
my_env["PYTHONIOENCODING"] = "utf-8"
process = subprocess.Popen(
f"python -i -q -u".split(),
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=0,
universal_newlines=True,
env=my_env
)
process.stdin.write("{code}" + "\\n")
process.stdin.write("exit()\\n")
process.stdin.flush()
try:
process.wait({timeout})
except Exception as e:
process.terminate()
for line in iter(process.stdout.readline, ""):
print(line)
for line in iter(process.stderr.readline, ""):
if line.startswith(">>>"):
continue
print(line)
"""
return runner
def _run_cmd(cmd: [str], work_dir: str, timeout: int) -> str:
if WIN32:
logger.warning("SIGALRM is not supported on Windows. No timeout will be enforced.")
result = subprocess.run(
cmd,
cwd=work_dir,
capture_output=True,
text=True,
)
else:
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(
subprocess.run,
cmd,
cwd=work_dir,
capture_output=True,
text=True,
)
result = future.result(timeout=timeout)
return result
def execute_code(
code: Optional[str] = None,
timeout: Optional[int] = None,
filename: Optional[str] = None,
work_dir: Optional[str] = None,
use_docker: Optional[Union[List[str], str, bool]] = None,
lang: Optional[str] = "python",
) -> Tuple[int, str]:
"""Execute code in a docker container.
This function is not tested on MacOS.
Args:
code (Optional, str): The code to execute.
If None, the code from the file specified by filename will be executed.
Either code or filename must be provided.
timeout (Optional, int): The maximum execution time in seconds.
If None, a default timeout will be used. The default timeout is 600 seconds. On Windows, the timeout is not enforced when use_docker=False.
filename (Optional, str): The file name to save the code or where the code is stored when `code` is None.
If None, a file with a randomly generated name will be created.
The randomly generated file will be deleted after execution.
The file name must be a relative path. Relative paths are relative to the working directory.
work_dir (Optional, str): The working directory for the code execution.
If None, a default working directory will be used.
The default working directory is the "extensions" directory under
"path_to_autogen".
use_docker (Optional, list, str or bool): The docker image to use for code execution.
If a list or a str of image name(s) is provided, the code will be executed in a docker container
with the first image successfully pulled.
If None, False or empty, the code will be executed in the current environment.
Default is None, which will be converted into an empty list when docker package is available.
Expected behaviour:
- If `use_docker` is explicitly set to True and the docker package is available, the code will run in a Docker container.
- If `use_docker` is explicitly set to True but the Docker package is missing, an error will be raised.
- If `use_docker` is not set (i.e., left default to None) and the Docker package is not available, a warning will be displayed, but the code will run natively.
If the code is executed in the current environment,
the code must be trusted.
lang (Optional, str): The language of the code. Default is "python".
Returns:
int: 0 if the code executes successfully.
str: The error message if the code fails to execute; the stdout otherwise.
"""
if all((code is None, filename is None)):
error_msg = f"Either {code=} or {filename=} must be provided."
logger.error(error_msg)
raise AssertionError(error_msg)
# Warn if use_docker was unspecified (or None), and cannot be provided (the default).
# In this case the current behavior is to fall back to run natively, but this behavior
# is subject to change.
if use_docker is None:
if docker is None:
use_docker = False
logger.warning(
"execute_code was called without specifying a value for use_docker. Since the python docker package is not available, code will be run natively. Note: this fallback behavior is subject to change"
)
else:
# Default to true
use_docker = True
timeout = timeout or DEFAULT_TIMEOUT
original_filename = filename
if WIN32 and lang in ["sh", "shell"] and (not use_docker):
lang = "ps1"
if filename is None:
code_hash = md5(code.encode()).hexdigest()
# create a file with a automatically generated name
filename = f"tmp_code_{code_hash}.{'py' if lang.startswith('python') else lang}"
if work_dir is None:
WORKING_DIR = os.path.join(AIStorage.get_instance().get_myai_dir(), "tmp_code")
pathlib.Path(WORKING_DIR).mkdir(exist_ok=True)
work_dir = os.path.join(WORKING_DIR, code_hash)
pathlib.Path(work_dir).mkdir(exist_ok=True)
filepath = os.path.join(work_dir, filename)
file_dir = os.path.dirname(filepath)
os.makedirs(file_dir, exist_ok=True)
if code is not None:
write_requirements(code, os.path.join(file_dir, "requirements.txt"))
code = create_runner(code, 30)
with open(filepath, "w", encoding="utf-8") as fout:
fout.write(code)
# check if already running in a docker container
in_docker_container = os.path.exists("/.dockerenv")
if not use_docker or in_docker_container:
try:
env_cmd = ["python", "-m", "venv", os.path.join(file_dir, "venv")]
_run_cmd(env_cmd, file_dir, timeout)
if WIN32:
venv_path = os.path.join(file_dir, "venv", "Scripts")
else:
venv_path = os.path.join(file_dir, "venv", "bin")
pip_cmd = [os.path.join(venv_path, "python"), "-m", "pip", "install", "-r", "requirements.txt"]
_run_cmd(pip_cmd, file_dir, timeout)
# already running in a docker container
cmd = [
os.path.join(venv_path, "python"),
f".\\{filename}" if WIN32 else filename,
]
result = _run_cmd(cmd, file_dir, timeout)
except TimeoutError:
if original_filename is None:
shutil.rmtree(os.path.join(file_dir, "venv"))
os.remove(filepath)
os.remove(os.path.join(file_dir, "requirements.txt"))
try:
os.removedirs(file_dir)
except Exception:
pass
return 1, TIMEOUT_MSG
if original_filename is None:
shutil.rmtree(os.path.join(file_dir, "venv"))
os.remove(filepath)
os.remove(os.path.join(file_dir, "requirements.txt"))
try:
os.removedirs(file_dir)
except Exception:
pass
if result.returncode:
logs = result.stderr
if original_filename is None:
abs_path = str(pathlib.Path(filepath).absolute())
logs = logs.replace(str(abs_path), "").replace(filename, "")
else:
abs_path = str(pathlib.Path(work_dir).absolute()) + PATH_SEPARATOR
logs = logs.replace(str(abs_path), "")
else:
logs = result.stdout
return result.returncode, logs
# create a docker client
client = docker.from_env()
image_list = (
["python:3-alpine", "python:3", "python:3-windowsservercore"]
if use_docker is True
else [use_docker]
if isinstance(use_docker, str)
else use_docker
)
for image in image_list:
# check if the image exists
try:
client.images.get(image)
break
except docker.errors.ImageNotFound:
# pull the image
logger.info("Pulling image", image)
try:
client.images.pull(image, stream=True, decode=True)
break
except docker.errors.DockerException as e:
logger.error("Failed to pull image", image)
logger.exception(e)
# get a randomized str based on current time to wrap the exit code
exit_code_str = f"exitcode{time.time()}"
start_str = f'start{time.time()}'
abs_path = pathlib.Path(work_dir).absolute()
cmd = [
"sh",
"-c",
f"pip install --quiet -r requirements.txt; echo -n {start_str}; {_cmd(lang)} {filename}; exit_code=$?; echo -n {exit_code_str}; echo -n $exit_code; echo {exit_code_str};",
]
# create a docker container
container = client.containers.run(
image,
command=cmd,
working_dir="/workspace",
detach=True,
# get absolute path to the working directory
volumes={abs_path: {"bind": "/workspace", "mode": "rw"}},
)
start_time = time.time()
while container.status != "exited" and time.time() - start_time < timeout:
# Reload the container object
container.reload()
if container.status != "exited":
container.stop()
container.remove()
if original_filename is None:
os.remove(filepath)
return 1, TIMEOUT_MSG, image
# get the container logs
logs: str = container.logs().decode("utf-8").rstrip()
start_pos = logs.find(start_str)
if start_pos != -1:
logs = logs[start_pos + len(start_str):]
# # commit the image
# tag = filename.replace("/", "")
# container.commit(repository="python", tag=tag)
# remove the container
container.remove()
# check if the code executed successfully
exit_code = container.attrs["State"]["ExitCode"]
if exit_code == 0:
# extract the exit code from the logs
pattern = re.compile(f"{exit_code_str}(\\d+){exit_code_str}")
match = pattern.search(logs)
exit_code = 1 if match is None else int(match.group(1))
# remove the exit code from the logs
logs = logs if match is None else pattern.sub("", logs)
if original_filename is None:
os.remove(filepath)
os.remove(os.path.join(file_dir, "requirements.txt"))
os.removedirs(file_dir)
if exit_code:
logs = logs.replace(f"/workspace/{filename if original_filename is None else ''}", "")
# return the exit code, logs and image
return exit_code, logs
class CodeInterpreterFunction(AIFunction):
def __init__(self):
self.func_id = "system.code_interpreter"
self.description = "execute python code"
self.parameters = ParameterDefine.create_parameters({
"code": {"type": "string", "description": "python code"}
})
def get_name(self) -> str:
return self.func_id
def get_description(self) -> str:
return self.description
def get_parameters(self) -> Dict:
return self.parameters
async def execute(self, **kwargs) -> str:
code = kwargs.get("code")
ret_code, result = execute_code(code=code)
if ret_code == 0:
return result.strip()
else:
return result.strip()
def is_local(self) -> bool:
return True
def is_in_zone(self) -> bool:
return True
def is_ready_only(self) -> bool:
return False
@@ -0,0 +1,56 @@
# pylint:disable=E0402
import json
from typing import Dict
from ..proto.ai_function import *
from ..agent.llm_context import GlobaToolsLibrary
from duckduckgo_search import AsyncDDGS
class DuckDuckGoTextSearchFunction(AIFunction):
def __init__(self):
self.name = "web.search.duckduckgo"
self.description = "Search web by text (powered by DuckDuckGo)"
self.region = "wt-wt"
self.safesearch = "moderate"
self.time = "y"
self.max_results = 5
self.parameters = ParameterDefine.create_parameters({
"query": {"type": "string", "description": "The query to search for."}
})
def register_function(self):
GlobaToolsLibrary.get_instance().register_tool_function(self)
def get_id(self) -> str:
return self.name
def get_description(self) -> str:
return self.description
def get_parameters(self) -> Dict:
return self.parameters
async def execute(self, **kwargs) -> str:
query = kwargs.get("query")
async with AsyncDDGS() as ddgs:
results = [r async for r in ddgs.text(
query,
region=self.region,
safesearch=self.safesearch,
timelimit=self.time,
backend="api",
max_results=self.max_results
)]
return json.dumps(results,,ensure_ascii=False)
def is_local(self) -> bool:
return True
def is_in_zone(self) -> bool:
return True
def is_ready_only(self) -> bool:
return False
@@ -0,0 +1,54 @@
# pylint:disable=E0402
import logging
from typing import Dict
from ..frame.compute_kernel import ComputeKernel
from ..proto.ai_function import *
from ..agent.llm_context import GlobaToolsLibrary
logger = logging.getLogger(__name__)
class Image2TextFunction(AIFunction):
def __init__(self):
self.func_id = "aigc.image_2_text"
self.description = "According to the input image file address, return the description of the image content"
self.parameters = ParameterDefine.create_parameters({
"image_path": {"type": "string", "description": "image file path"}
})
logger.info(f"init Image2TextFunction")
def register_function(self):
GlobaToolsLibrary.get_instance().register_tool_function(self)
def get_id(self) -> str:
return self.func_id
def get_description(self) -> str:
return self.description
def get_parameters(self) -> Dict:
return self.parameters
async def execute(self, **kwargs) -> str:
logger.info(f"execute image_2_text function: {kwargs}")
image_path = kwargs.get("image_path")
data = await ComputeKernel.get_instance().do_image_2_text(image_path, '')
try:
result = data['message']['choices'][0]['message']['content']
except (KeyError, TypeError, IndexError):
logger.error(f"image_2_text error: {data}")
result = ""
return result
def is_local(self) -> bool:
return False
def is_in_zone(self) -> bool:
return True
def is_ready_only(self) -> bool:
return False
@@ -1,57 +1,66 @@
# pylint:disable=E0402
import io import io
import logging import logging
import os import os
import random import random
from pathlib import Path
from typing import Dict from typing import Dict
from aios_kernel import ComputeKernel from ..proto.ai_function import *
from aios_kernel.ai_function import AIFunction from ..frame.compute_kernel import ComputeKernel
from ..storage.storage import AIStorage
from pydub import AudioSegment from pydub import AudioSegment
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class TextToSpeechFunction(AIFunction):
def __init__(self):
self.func_id = "text_to_speech"
self.description = "根据输入的剧本生成音频文件,成功时会返回音频文件路径"
def get_name(self) -> str: class ScriptToSpeechFunction(AIFunction):
def __init__(self):
self.func_id = "aigc.script_to_speech"
self.description = "Generate audio files according to the input script, and the audio file path will be returned when successful"
self.speech_path = os.path.join(AIStorage.get_instance().get_myai_dir(), "tts")
self.parameters = ParameterDefine.create_parameters({
"language": {"type": "string", "description": "Actual language", "enum": ["zh", "en"]},
"model": {"type": "string", "description": "Studio", "enum": ["tts-1", "tts-1-hd"]},
"roles": {"type": "array", "items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Character name"},
"gender": {"type": "string", "description": "Gender", "enum": ["man", "female"]},
"age": {"type": "string", "description": "age", "enum": ["child", "adult"]},
}}},
"lines": {"type": "array", "items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Character name"},
"tone": {"type": "string", "description": "Sovereign emotions",
"enum": ["happy", "sad", "angry", "fear", "disgust", "surprise", "neutral"]},
"text": {"type": "string", "description": "Line"},
}
}}
})
Path(self.speech_path).mkdir(exist_ok=True)
def get_id(self) -> str:
return self.func_id return self.func_id
def get_description(self) -> str: def get_description(self) -> str:
return self.description return self.description
def get_parameters(self) -> Dict: def get_parameters(self):
return { return self.parameters
"type": "object",
"properties": {
"language": {"type": "string", "description": "演播语言", "enum": ["zh", "en"]},
"roles": {"type": "array", "items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "角色名字"},
"gender": {"type": "string", "description": "角色性别", "enum": ["man", "female"]},
"age": {"type": "string", "description": "年龄", "enum": ["child", "adult"]},
}}},
"lines": {"type": "array", "items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "角色名字"},
"tone": {"type": "string", "description": "演播情感",
"enum": ["happy", "sad", "angry", "fear", "disgust", "surprise", "neutral"]},
"text": {"type": "string", "description": "台词"},
}
}}
}
}
async def execute(self, **kwargs) -> str: async def execute(self, **kwargs) -> str:
logger.info(f"execute text_to_speech function: {kwargs}") logger.info(f"execute aigc.script_to_speech function: {kwargs}")
language = kwargs.get("language") language = kwargs.get("language")
if language is None: if language is None:
language = "zh" language = "zh"
model = kwargs.get("model")
roles = kwargs.get("roles") roles = kwargs.get("roles")
lines = kwargs.get("lines") lines = kwargs.get("lines")
@@ -71,23 +80,23 @@ class TextToSpeechFunction(AIFunction):
i = 0 i = 0
while i < 3: while i < 3:
try: try:
data = await ComputeKernel.get_instance().do_text_to_speech(text, language, gender, age, name, tone) data = await ComputeKernel.get_instance().do_text_to_speech(text, language, gender, age, name, tone, model_name=model)
if audio is None: if audio is None:
audio = AudioSegment.from_mp3(io.BytesIO(data)) audio = AudioSegment.from_mp3(io.BytesIO(data))
else: else:
audio = audio + AudioSegment.from_mp3(io.BytesIO(data)) audio = audio + AudioSegment.from_mp3(io.BytesIO(data))
break break
except Exception as e: except Exception as e:
logger.error(f"do_text_to_speech failed: {e}") logger.error(f"script_to_speech failed: {e}")
i += 1 i += 1
continue continue
if audio is not None: if audio is not None:
path = os.path.join(os.path.realpath(os.curdir), "{}.mp3".format(''.join(random.sample('zyxwvutsrqponmlkjihgfedcba', 10)))) path = os.path.join(self.speech_path, "{}.mp3".format(''.join(random.sample('zyxwvutsrqponmlkjihgfedcba', 10))))
audio.export(path, format="mp3") audio.export(path, format="mp3")
return "exec text_to_speech OKspeech file store at {}".format(path) return "exec script_to_speech OKspeech file store at ```{}```".format(path)
else: else:
return "exec text_to_speech failed" return "exec script_to_speech failed"
def is_local(self) -> bool: def is_local(self) -> bool:
return True return True
@@ -0,0 +1,113 @@
# pylint:disable=E0402
from datetime import timedelta, datetime
from typing import Dict
from cachetools import TLRUCache, cached
from ..proto.ai_function import *
from ..environment.sql_database import SQLDatabase, get_from_env
def _my_ttu(_key, _value, now):
return now + timedelta(seconds=600)
database_cache = TLRUCache(ttu=_my_ttu, maxsize=10000, timer=datetime.now)
@cached(cache=database_cache)
def get_database(uri: str) -> SQLDatabase:
return SQLDatabase.from_uri(uri)
class GetTableInfosFunction(AIFunction):
def __init__(self):
super().__init__()
self.name = "get_table_infos"
self.description = "Get table informations in the database"
def get_id(self) -> str:
return self.name
def get_description(self) -> str:
return self.description
def get_parameters(self) -> Dict:
return {
"type": "object",
"properties": {
"database_url": {"type": "string", "description": "Database URL,Can be set to None"},
}
}
async def execute(self, **kwargs) -> str:
database_url: str = kwargs.get("database_url")
if (database_url is None
or database_url.strip() == ""
or database_url.strip().lower() == "none"
or database_url.strip().lower() == "null"):
database_url = get_from_env(key="database url", env_key="DATABASE_URL")
if database_url is None:
return "error: database_url is None"
database = get_database(database_url)
tables = database.get_usable_table_names()
table_infos = database.get_table_info(tables)
return table_infos
def is_local(self) -> bool:
return True
def is_in_zone(self) -> bool:
return True
def is_ready_only(self) -> bool:
return False
class ExecuteSqlFunction(AIFunction):
def __init__(self):
super().__init__()
self.name = "execute_sql"
self.description = """
Input to this function is a detailed and correct SQL query, output is a result from the database.
If the query is not correct, an error message will be returned.
If an error is returned, rewrite the query, check the query, and try again.
"""
def get_id(self) -> str:
return self.name
def get_description(self) -> str:
return self.description
def get_parameters(self) -> Dict:
return {
"type": "object",
"properties": {
"database_url": {"type": "string", "description": "Database URL,Can be set to None"},
"sql": {"type": "string", "description": "SQL to execute"}
}
}
async def execute(self, **kwargs) -> str:
database_url = kwargs.get("database_url")
if (database_url is None
or database_url.strip() == ""
or database_url.strip().lower() == "none"
or database_url.strip().lower() == "null"):
database_url = get_from_env(key="database url", env_key="DATABASE_URL")
if database_url is None:
return "error: database_url is None"
sql = kwargs.get("sql")
database = get_database(database_url)
return database.run_no_throw(sql)
def is_local(self) -> bool:
return True
def is_in_zone(self) -> bool:
return True
def is_ready_only(self) -> bool:
return False
@@ -0,0 +1,77 @@
import io
import logging
import os
import random
from pathlib import Path
from typing import Dict
from ..proto.ai_function import *
from ..frame.compute_kernel import ComputeKernel
from ..storage.storage import AIStorage
from pydub import AudioSegment
logger = logging.getLogger(__name__)
class TextToSpeechFunction(AIFunction):
def __init__(self):
self.func_id = "aigc.text_to_speech"
self.description = "To generate audio files according to the input text, the audio file path will be returned when successful"
self.speech_path = os.path.join(AIStorage.get_instance().get_myai_dir(), "tts")
self.parameters = ParameterDefine.create_parameters({
"language": {"type": "string", "description": "Actual language", "enum": ["zh", "en"]},
"model": {"type": "string", "description": "Studio", "enum": ["tts-1", "tts-1-hd"]},
"text": {"type": "string", "description": "text"}
})
Path(self.speech_path).mkdir(exist_ok=True)
def get_id(self) -> str:
return self.func_id
def get_description(self) -> str:
return self.description
def get_parameters(self) -> Dict:
return self.parameters
async def execute(self, **kwargs) -> str:
logger.info(f"execute aigc.text_to_speech function: {kwargs}")
language = kwargs.get("language")
if language is None:
language = "en"
model = kwargs.get("model")
text = kwargs.get("text")
i = 0
while i < 3:
try:
data = await ComputeKernel.get_instance().do_text_to_speech(text, language, None, None, None, None,
model_name=model)
if data is not None:
audio = AudioSegment.from_mp3(io.BytesIO(data))
break
except Exception as e:
logger.error(f"do_text_to_speech failed: {e}")
i += 1
continue
if audio is not None:
path = os.path.join(self.speech_path, "{}.mp3".format(''.join(random.sample('zyxwvutsrqponmlkjihgfedcba', 10))))
audio.export(path, format="mp3")
return "exec text_to_speech OKspeech file store at ```{}```".format(path)
else:
return "exec text_to_speech failed"
def is_local(self) -> bool:
return True
def is_in_zone(self) -> bool:
return True
def is_ready_only(self) -> bool:
return False
+123
View File
@@ -0,0 +1,123 @@
# basic environment class
# we have some built-in environment: Calender(include timer),Home(connect to IoT device in your home), ,KnwoledgeBase,FileSystem,
# pylint:disable=E0402
from abc import ABC, abstractmethod
from typing import Any, Callable, Optional,Dict,Awaitable,List
import logging
from ..proto.ai_function import *
logger = logging.getLogger(__name__)
class BaseEnvironment:
@classmethod
def get_env_by_id(cls,env_id:str)->'BaseEnvironment':
if cls.all_env is None:
cls.all_env = {}
return cls.all_env.get(env_id)
@classmethod
def register_env(cls,env_id:str,env:'BaseEnvironment')->None:
if cls.all_env is None:
cls.all_env = {}
cls.all_env[env_id] = env
def __init__(self, workspace: str) -> None:
pass
# @abstractmethod
# #TODO: how to use env? different env has different prompt
# def get_env_prompt(self) -> str:
# pass
@abstractmethod
def get_ai_function(self,func_name:str) -> AIFunction:
pass
@abstractmethod
def get_all_ai_functions(self) -> List[AIFunction]:
pass
@abstractmethod
def get_ai_operation(self,op_name:str) -> AIAction:
pass
@abstractmethod
def get_all_ai_operations(self) -> List[AIAction]:
pass
def __getitem__(self, key):
return self.get_value(key)
@abstractmethod
def get_value(self,key:str) -> Optional[str]:
pass
# _all_env = {}
# @classmethod
# def get_env_by_id(cls,env_id:str):
# return cls._all_env.get(env_id)
# @classmethod
# def set_env_by_id(cls,id,env):
# assert id == env.get_id()
# cls._all_env[env.get_id()] = env
class SimpleEnvironment(BaseEnvironment):
def __init__(self, workspace: str) -> None:
super().__init__(workspace)
self.functions: Dict[str,AIFunction] = {}
self.operations: Dict[str,AIAction] = {}
def add_ai_function(self,func:AIFunction) -> None:
self.functions[func.get_id()] = func
def get_ai_function(self,func_name:str) -> AIFunction:
func = self.functions.get(func_name)
if func is not None:
return func
return None
def get_all_ai_functions(self) -> List[AIFunction]:
func_list = []
func_list.extend(self.functions.values())
return func_list
def add_ai_operation(self,op:AIAction) -> None:
self.operations[op.get_id()] = op
def get_ai_operation(self,op_name:str) -> AIAction:
op = self.operations.get(op_name)
if op is not None:
return op
return None
def get_all_ai_operations(self) -> List[AIAction]:
op_list = []
op_list.extend(self.operations.values())
return op_list
class CompositeEnvironment(SimpleEnvironment):
def __init__(self, workspace: str) -> None:
super().__init__(workspace)
self.envs: List[BaseEnvironment] = []
def add_env(self, env: BaseEnvironment) -> None:
self.envs.append(env)
functions = env.get_all_ai_functions()
for func in functions:
self.functions[func.get_id()] = func
operations = env.get_all_ai_operations()
for op in operations:
self.operations[op.get_id()] = op
def get_value(self,key:str) -> Optional[str]:
for env in self.envs:
val = env.get_value(key)
if val is not None:
return val
+228
View File
@@ -0,0 +1,228 @@
# pylint:disable=E0402
import sqlite3
import json
import threading
import logging
from datetime import datetime
from typing import Optional, List
logger = logging.getLogger(__name__)
class SimpleKnowledgeDB:
def __init__(self,db_path:str):
self.db_path = db_path
self._get_conn()
def _get_conn(self):
""" get db connection """
local = threading.local()
if not hasattr(local, 'conn'):
local.conn = self._create_connection(self.db_path)
return local.conn
def _create_connection(self, db_file):
""" create a database connection to a SQLite database """
conn = None
try:
conn = sqlite3.connect(db_file)
except Exception as e:
logger.error("Error occurred while connecting to database: %s", e)
return None
if conn:
self._create_tables(conn)
return conn
def _create_tables(self,conn):
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS documents (
doc_path TEXT PRIMARY KEY,
length INTEGER,
last_modify TEXT,
doc_hash TEXT,
create_time TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS knowledge (
doc_hash TEXT PRIMARY KEY,
title TEXT,
summary TEXT,
content TEXT,
catalogs TEXT,
tags TEXT,
llm_title TEXT,
llm_summary TEXT,
create_time TEXT
)
''')
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_documents_doc_hash
ON documents (doc_hash)
''')
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_knowledge_tags
ON knowledge (tags)
''')
conn.commit()
def add_doc(self, doc_path: str, length: int, last_modify: str, doc_hash: Optional[str] = None):
conn = self._get_conn()
cursor = conn.cursor()
create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
cursor.execute('''
INSERT INTO documents (doc_path, length, last_modify, doc_hash,create_time)
VALUES (?, ?, ?, ?,?)
''', (doc_path, length, last_modify, doc_hash,create_time))
conn.commit()
def is_doc_exist(self, doc_path: str) -> bool:
conn = self._get_conn()
cursor = conn.cursor()
cursor.execute('''
SELECT doc_path
FROM documents
WHERE doc_path = ?
''', (doc_path,))
return len(cursor.fetchall()) > 0
def set_doc_hash(self, doc_path: str, doc_hash: str):
conn = self._get_conn()
cursor = conn.cursor()
cursor.execute('''
UPDATE documents
SET doc_hash = ?
WHERE doc_path = ?
''', (doc_hash, doc_path))
conn.commit()
def get_docs_without_hash(self,limit:int=1024) -> List[str]:
conn = self._get_conn()
cursor = conn.cursor()
cursor.execute('''
SELECT doc_path
FROM documents
WHERE doc_hash IS NULL OR doc_hash = ''
ORDER BY create_time DESC
LIMIT ?
''',(limit,))
return [row[0] for row in cursor.fetchall()]
#metadata["summary"]
#metadata["catelogs"]
#metadata["tags"]
def add_knowledge(self, doc_hash: str, title: str, metadata: dict,content:str = None,):
conn = self._get_conn()
cursor = conn.cursor()
create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
summary = metadata.get("summary", "")
catalogs = metadata.get("catalogs","")
tags = ','.join(metadata.get("tags", []))
cursor.execute('''
INSERT INTO knowledge (doc_hash, title , summary , catalogs , tags,create_time)
VALUES (?, ?, ?, ?, ?,?)
''', (doc_hash, title, summary, catalogs, tags,create_time))
conn.commit()
#llm_result["summary"]
#llm_result["tags"]
#llm_result["catelog"]
def set_knowledge_llm_result(self, doc_hash: str, llm_result: dict):
conn = self._get_conn()
cursor = conn.cursor()
title = llm_result.get("title", "")
summary = llm_result.get("summary", "")
catalogs = json.dumps(llm_result.get("catalogs", {}))
tags = ','.join(llm_result.get("tags", []))
cursor.execute('''
UPDATE knowledge
SET llm_title = ?,llm_summary = ?, catalogs = ?, tags = ?
WHERE doc_hash = ?
''', (title,summary, catalogs, tags, doc_hash))
conn.commit()
def get_hash_by_doc_path(self, doc_path: str) -> Optional[str]:
conn = self._get_conn()
cursor = conn.cursor()
cursor.execute('''
SELECT doc_hash
FROM documents
WHERE doc_path = ?
''', (doc_path,))
row = cursor.fetchone()
if row is None:
return None
return row[0]
def get_knowledge(self, doc_hash: str) -> Optional[dict]:
conn = self._get_conn()
cursor = conn.cursor()
cursor.execute('''
SELECT title, summary, catalogs, tags, llm_title, llm_summary
FROM knowledge
WHERE doc_hash = ?
''', (doc_hash,))
row = cursor.fetchone()
if row is None:
return None
# get doc path
cursor.execute('''
SELECT doc_path
FROM documents
WHERE doc_hash = ?
''', (doc_hash,))
row2 = cursor.fetchone()
if row2 is None:
return None
doc_path = row2[0]
return {
"full_path": doc_path,
"title": row[0],
"summary": row[1],
"catalogs": row[2],
"tags": row[3],
"llm_title" : row[4],
"llm_summary" : row[5],
}
def get_knowledge_without_llm_title(self,limit:int=16) -> List[str]:
conn = self._get_conn()
cursor = conn.cursor()
cursor.execute('''
SELECT doc_hash
FROM knowledge
WHERE llm_title IS NULL OR llm_title = ''
ORDER BY create_time DESC
LIMIT ?
''',(limit,))
return [row[0] for row in cursor.fetchall()]
def query_docs_by_tag(self, tag: str) -> List[str]:
conn = self._get_conn()
cursor = conn.cursor()
tag_json = json.dumps(tag,ensure_ascii=False) # 将标签转换为 JSON 字符串
cursor.execute('''
SELECT documents.doc_path
FROM documents
JOIN knowledge ON documents.doc_hash = knowledge.doc_hash
WHERE json_extract(knowledge.tags, '$') LIKE ?
''', (tag))
return [row[0] for row in cursor.fetchall()]
def query(self,sql:str):
pass
#cursor = self.conn.cursor()
+495
View File
@@ -0,0 +1,495 @@
"""
Taken from: langchain
SQLAlchemy wrapper around a database.
"""
# pylint:disable=E0402
from __future__ import annotations
import os
import warnings
from typing import Any, Dict, Iterable, List, Literal, Optional, Sequence, Union
import sqlalchemy
from sqlalchemy import MetaData, Table, create_engine, inspect, select, text
from sqlalchemy.engine import Engine
from sqlalchemy.exc import ProgrammingError, SQLAlchemyError
from sqlalchemy.schema import CreateTable
from sqlalchemy.types import NullType
def get_from_env(key: str, env_key: str, default: Optional[str] = None) -> str:
"""Get a value from a dictionary or an environment variable."""
if env_key in os.environ and os.environ[env_key]:
return os.environ[env_key]
elif default is not None:
return default
else:
raise ValueError(
f"Did not find {key}, please add an environment variable"
f" `{env_key}` which contains it, or pass"
f" `{key}` as a named parameter."
)
def _format_index(index: sqlalchemy.engine.interfaces.ReflectedIndex) -> str:
return (
f'Name: {index["name"]}, Unique: {index["unique"]},'
f' Columns: {str(index["column_names"])}'
)
def truncate_word(content: Any, *, length: int, suffix: str = "...") -> str:
"""
Truncate a string to a certain number of words, based on the max string
length.
"""
if not isinstance(content, str) or length <= 0:
return content
if len(content) <= length:
return content
return content[: length - len(suffix)].rsplit(" ", 1)[0] + suffix
class SQLDatabase:
"""SQLAlchemy wrapper around a database."""
def __init__(
self,
engine: Engine,
schema: Optional[str] = None,
metadata: Optional[MetaData] = None,
ignore_tables: Optional[List[str]] = None,
include_tables: Optional[List[str]] = None,
sample_rows_in_table_info: int = 3,
indexes_in_table_info: bool = False,
custom_table_info: Optional[dict] = None,
view_support: bool = False,
max_string_length: int = 300,
):
"""Create engine from database URI."""
self._engine = engine
self._schema = schema
if include_tables and ignore_tables:
raise ValueError("Cannot specify both include_tables and ignore_tables")
self._inspector = inspect(self._engine)
# including view support by adding the views as well as tables to the all
# tables list if view_support is True
self._all_tables = set(
self._inspector.get_table_names(schema=schema)
+ (self._inspector.get_view_names(schema=schema) if view_support else [])
)
self._include_tables = set(include_tables) if include_tables else set()
if self._include_tables:
missing_tables = self._include_tables - self._all_tables
if missing_tables:
raise ValueError(
f"include_tables {missing_tables} not found in database"
)
self._ignore_tables = set(ignore_tables) if ignore_tables else set()
if self._ignore_tables:
missing_tables = self._ignore_tables - self._all_tables
if missing_tables:
raise ValueError(
f"ignore_tables {missing_tables} not found in database"
)
usable_tables = self.get_usable_table_names()
self._usable_tables = set(usable_tables) if usable_tables else self._all_tables
if not isinstance(sample_rows_in_table_info, int):
raise TypeError("sample_rows_in_table_info must be an integer")
self._sample_rows_in_table_info = sample_rows_in_table_info
self._indexes_in_table_info = indexes_in_table_info
self._custom_table_info = custom_table_info
if self._custom_table_info:
if not isinstance(self._custom_table_info, dict):
raise TypeError(
"table_info must be a dictionary with table names as keys and the "
"desired table info as values"
)
# only keep the tables that are also present in the database
intersection = set(self._custom_table_info).intersection(self._all_tables)
self._custom_table_info = dict(
(table, self._custom_table_info[table])
for table in self._custom_table_info
if table in intersection
)
self._max_string_length = max_string_length
self._metadata = metadata or MetaData()
# including view support if view_support = true
self._metadata.reflect(
views=view_support,
bind=self._engine,
only=list(self._usable_tables),
schema=self._schema,
)
@classmethod
def from_uri(
cls, database_uri: str, engine_args: Optional[dict] = None, **kwargs: Any
) -> SQLDatabase:
"""Construct a SQLAlchemy engine from URI."""
_engine_args = engine_args or {}
return cls(create_engine(database_uri, **_engine_args), **kwargs)
@classmethod
def from_databricks(
cls,
catalog: str,
schema: str,
host: Optional[str] = None,
api_token: Optional[str] = None,
warehouse_id: Optional[str] = None,
cluster_id: Optional[str] = None,
engine_args: Optional[dict] = None,
**kwargs: Any,
) -> SQLDatabase:
"""
Class method to create an SQLDatabase instance from a Databricks connection.
This method requires the 'databricks-sql-connector' package. If not installed,
it can be added using `pip install databricks-sql-connector`.
Args:
catalog (str): The catalog name in the Databricks database.
schema (str): The schema name in the catalog.
host (Optional[str]): The Databricks workspace hostname, excluding
'https://' part. If not provided, it attempts to fetch from the
environment variable 'DATABRICKS_HOST'. If still unavailable and if
running in a Databricks notebook, it defaults to the current workspace
hostname. Defaults to None.
api_token (Optional[str]): The Databricks personal access token for
accessing the Databricks SQL warehouse or the cluster. If not provided,
it attempts to fetch from 'DATABRICKS_TOKEN'. If still unavailable
and running in a Databricks notebook, a temporary token for the current
user is generated. Defaults to None.
warehouse_id (Optional[str]): The warehouse ID in the Databricks SQL. If
provided, the method configures the connection to use this warehouse.
Cannot be used with 'cluster_id'. Defaults to None.
cluster_id (Optional[str]): The cluster ID in the Databricks Runtime. If
provided, the method configures the connection to use this cluster.
Cannot be used with 'warehouse_id'. If running in a Databricks notebook
and both 'warehouse_id' and 'cluster_id' are None, it uses the ID of the
cluster the notebook is attached to. Defaults to None.
engine_args (Optional[dict]): The arguments to be used when connecting
Databricks. Defaults to None.
**kwargs (Any): Additional keyword arguments for the `from_uri` method.
Returns:
SQLDatabase: An instance of SQLDatabase configured with the provided
Databricks connection details.
Raises:
ValueError: If 'databricks-sql-connector' is not found, or if both
'warehouse_id' and 'cluster_id' are provided, or if neither
'warehouse_id' nor 'cluster_id' are provided and it's not executing
inside a Databricks notebook.
"""
try:
from databricks import sql # noqa: F401
except ImportError:
raise ValueError(
"databricks-sql-connector package not found, please install with"
" `pip install databricks-sql-connector`"
)
context = None
try:
from dbruntime.databricks_repl_context import get_context
context = get_context()
except ImportError:
pass
default_host = context.browserHostName if context else None
if host is None:
host = get_from_env("host", "DATABRICKS_HOST", default_host)
default_api_token = context.apiToken if context else None
if api_token is None:
api_token = get_from_env("api_token", "DATABRICKS_TOKEN", default_api_token)
if warehouse_id is None and cluster_id is None:
if context:
cluster_id = context.clusterId
else:
raise ValueError(
"Need to provide either 'warehouse_id' or 'cluster_id'."
)
if warehouse_id and cluster_id:
raise ValueError("Can't have both 'warehouse_id' or 'cluster_id'.")
if warehouse_id:
http_path = f"/sql/1.0/warehouses/{warehouse_id}"
else:
http_path = f"/sql/protocolv1/o/0/{cluster_id}"
uri = (
f"databricks://token:{api_token}@{host}?"
f"http_path={http_path}&catalog={catalog}&schema={schema}"
)
return cls.from_uri(database_uri=uri, engine_args=engine_args, **kwargs)
@classmethod
def from_cnosdb(
cls,
url: str = "127.0.0.1:8902",
user: str = "root",
password: str = "",
tenant: str = "cnosdb",
database: str = "public",
) -> SQLDatabase:
"""
Class method to create an SQLDatabase instance from a CnosDB connection.
This method requires the 'cnos-connector' package. If not installed, it
can be added using `pip install cnos-connector`.
Args:
url (str): The HTTP connection host name and port number of the CnosDB
service, excluding "http://" or "https://", with a default value
of "127.0.0.1:8902".
user (str): The username used to connect to the CnosDB service, with a
default value of "root".
password (str): The password of the user connecting to the CnosDB service,
with a default value of "".
tenant (str): The name of the tenant used to connect to the CnosDB service,
with a default value of "cnosdb".
database (str): The name of the database in the CnosDB tenant.
Returns:
SQLDatabase: An instance of SQLDatabase configured with the provided
CnosDB connection details.
"""
try:
from cnosdb_connector import make_cnosdb_langchain_uri
uri = make_cnosdb_langchain_uri(url, user, password, tenant, database)
return cls.from_uri(database_uri=uri)
except ImportError:
raise ValueError(
"cnos-connector package not found, please install with"
" `pip install cnos-connector`"
)
@property
def dialect(self) -> str:
"""Return string representation of dialect to use."""
return self._engine.dialect.name
def get_usable_table_names(self) -> Iterable[str]:
"""Get names of tables available."""
if self._include_tables:
return sorted(self._include_tables)
return sorted(self._all_tables - self._ignore_tables)
def get_table_names(self) -> Iterable[str]:
"""Get names of tables available."""
warnings.warn(
"This method is deprecated - please use `get_usable_table_names`."
)
return self.get_usable_table_names()
@property
def table_info(self) -> str:
"""Information about all tables in the database."""
return self.get_table_info()
def get_table_info(self, table_names: Optional[List[str]] = None) -> str:
"""Get information about specified tables.
Follows best practices as specified in: Rajkumar et al, 2022
(https://arxiv.org/abs/2204.00498)
If `sample_rows_in_table_info`, the specified number of sample rows will be
appended to each table description. This can increase performance as
demonstrated in the paper.
"""
all_table_names = self.get_usable_table_names()
if table_names is not None:
missing_tables = set(table_names).difference(all_table_names)
if missing_tables:
raise ValueError(f"table_names {missing_tables} not found in database")
all_table_names = table_names
meta_tables = [
tbl
for tbl in self._metadata.sorted_tables
if tbl.name in set(all_table_names)
and not (self.dialect == "sqlite" and tbl.name.startswith("sqlite_"))
]
tables = []
for table in meta_tables:
if self._custom_table_info and table.name in self._custom_table_info:
tables.append(self._custom_table_info[table.name])
continue
# Ignore JSON datatyped columns
for k, v in table.columns.items():
if type(v.type) is NullType:
table._columns.remove(v)
# add create table command
create_table = str(CreateTable(table).compile(self._engine))
table_info = f"{create_table.rstrip()}"
has_extra_info = (
self._indexes_in_table_info or self._sample_rows_in_table_info
)
if has_extra_info:
table_info += "\n\n/*"
if self._indexes_in_table_info:
table_info += f"\n{self._get_table_indexes(table)}\n"
if self._sample_rows_in_table_info:
table_info += f"\n{self._get_sample_rows(table)}\n"
if has_extra_info:
table_info += "*/"
tables.append(table_info)
tables.sort()
final_str = "\n\n".join(tables)
return final_str
def _get_table_indexes(self, table: Table) -> str:
indexes = self._inspector.get_indexes(table.name)
indexes_formatted = "\n".join(map(_format_index, indexes))
return f"Table Indexes:\n{indexes_formatted}"
def _get_sample_rows(self, table: Table) -> str:
# build the select command
command = select(table).limit(self._sample_rows_in_table_info)
# save the columns in string format
columns_str = "\t".join([col.name for col in table.columns])
try:
# get the sample rows
with self._engine.connect() as connection:
sample_rows_result = connection.execute(command) # type: ignore
# shorten values in the sample rows
sample_rows = list(
map(lambda ls: [str(i)[:100] for i in ls], sample_rows_result)
)
# save the sample rows in string format
sample_rows_str = "\n".join(["\t".join(row) for row in sample_rows])
# in some dialects when there are no rows in the table a
# 'ProgrammingError' is returned
except ProgrammingError:
sample_rows_str = ""
return (
f"{self._sample_rows_in_table_info} rows from {table.name} table:\n"
f"{columns_str}\n"
f"{sample_rows_str}"
)
def _execute(
self,
command: str,
fetch: Union[Literal["all"], Literal["one"]] = "all",
) -> Sequence[Dict[str, Any]]:
"""
Executes SQL command through underlying engine.
If the statement returns no rows, an empty list is returned.
"""
with self._engine.begin() as connection:
if self._schema is not None:
if self.dialect == "snowflake":
connection.exec_driver_sql(
"ALTER SESSION SET search_path = %s", (self._schema,)
)
elif self.dialect == "bigquery":
connection.exec_driver_sql("SET @@dataset_id=?", (self._schema,))
elif self.dialect == "mssql":
pass
elif self.dialect == "trino":
connection.exec_driver_sql("USE ?", (self._schema,))
elif self.dialect == "duckdb":
# Unclear which parameterized argument syntax duckdb supports.
# The docs for the duckdb client say they support multiple,
# but `duckdb_engine` seemed to struggle with all of them:
# https://github.com/Mause/duckdb_engine/issues/796
connection.exec_driver_sql(f"SET search_path TO {self._schema}")
elif self.dialect == "oracle":
connection.exec_driver_sql(
f"ALTER SESSION SET CURRENT_SCHEMA = {self._schema}"
)
else: # postgresql and other compatible dialects
connection.exec_driver_sql("SET search_path TO %s", (self._schema,))
cursor = connection.execute(text(command))
if cursor.returns_rows:
if fetch == "all":
result = [x._asdict() for x in cursor.fetchall()]
elif fetch == "one":
first_result = cursor.fetchone()
result = [] if first_result is None else [first_result._asdict()]
else:
raise ValueError("Fetch parameter must be either 'one' or 'all'")
return result
return []
def run(
self,
command: str,
fetch: Union[Literal["all"], Literal["one"]] = "all",
) -> str:
"""Execute a SQL command and return a string representing the results.
If the statement returns rows, a string of the results is returned.
If the statement returns no rows, an empty string is returned.
"""
result = self._execute(command, fetch)
# Convert columns values to string to avoid issues with sqlalchemy
# truncating text
res = [
tuple(truncate_word(c, length=self._max_string_length) for c in r.values())
for r in result
]
if not res:
return ""
else:
return str(res)
def get_table_info_no_throw(self, table_names: Optional[List[str]] = None) -> str:
"""Get information about specified tables.
Follows best practices as specified in: Rajkumar et al, 2022
(https://arxiv.org/abs/2204.00498)
If `sample_rows_in_table_info`, the specified number of sample rows will be
appended to each table description. This can increase performance as
demonstrated in the paper.
"""
try:
return self.get_table_info(table_names)
except ValueError as e:
"""Format the error message"""
return f"Error: {e}"
def run_no_throw(
self,
command: str,
fetch: Union[Literal["all"], Literal["one"]] = "all",
) -> str:
"""Execute a SQL command and return a string representing the results.
If the statement returns rows, a string of the results is returned.
If the statement returns no rows, an empty string is returned.
If the statement throws an error, the error message is returned.
"""
try:
return self.run(command, fetch)
except SQLAlchemyError as e:
"""Format the error message"""
return f"Error: {e}"
@@ -1,4 +1,4 @@
# pylint:disable=E0402
from datetime import datetime from datetime import datetime
import asyncio import asyncio
import json import json
@@ -7,20 +7,22 @@ from sqlite3 import Error
import threading import threading
import logging import logging
from typing import Optional from typing import Optional
from .text_to_speech_function import TextToSpeechFunction
from .compute_kernel import ComputeKernel, ComputeTaskResultCode
from .environment import Environment,EnvironmentEvent
from .ai_function import SimpleAIFunction
from .storage import AIStorage
from .contact_manager import ContactManager,Contact,FamilyMember
import aiosqlite import aiosqlite
from ..agent.llm_context import GlobaToolsLibrary
from ..proto.compute_task import *
from ..proto.ai_function import *
from ..frame.compute_kernel import ComputeKernel
from ..frame.contact_manager import ContactManager,Contact
from ..storage.storage import AIStorage
from .environment import SimpleEnvironment, CompositeEnvironment
from ..ai_functions.script_to_speech_function import ScriptToSpeechFunction
from ..ai_functions.image_2_text_function import Image2TextFunction
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class CalenderEvent(SimpleEnvironment):
class CalenderEvent(EnvironmentEvent):
def __init__(self,data) -> None: def __init__(self,data) -> None:
super().__init__() super().__init__()
self.event_name = "timer" self.event_name = "timer"
@@ -30,48 +32,45 @@ class CalenderEvent(EnvironmentEvent):
return f"#event timer:{self.data}" return f"#event timer:{self.data}"
# AI Calender GOAL: Let user use "create notify after 2 days" to create a timer event # AI Calender GOAL: Let user use "create notify after 2 days" to create a timer event
class CalenderEnvironment(Environment): class CalenderEnvironment(SimpleEnvironment):
def __init__(self, env_id: str) -> None: def __init__(self, env_id: str) -> None:
super().__init__(env_id) super().__init__(env_id)
self.db_file = AIStorage.get_instance().get_myai_dir() / "calender.db" self.db_file = AIStorage.get_instance().get_myai_dir() / "calender.db"
self.is_run = False self.is_run = False
gl = GlobaToolsLibrary.get_instance()
self.add_ai_function(SimpleAIFunction("get_time", gl.register_tool_function(SimpleAIFunction("system.now",
"get current time", "get current time",
self._get_now)) self._get_now))
#self.add_ai_function(SimpleAIFunction("serach_events", get_param = ParameterDefine.create_parameters({
# "search events in calender",
# self._search_events))
get_param = {
"start_time": "start time (UTC) of event", "start_time": "start time (UTC) of event",
"end_time": "end time (UTC) of event" "end_time": "end time (UTC) of event"
} })
self.add_ai_function(SimpleAIFunction("get_events", gl.register_tool_function(SimpleAIFunction("system.calender.get_events",
"get events in calender by time range", "get events in calender by time range",
self._get_events_by_time_range,get_param)) self._get_events_by_time_range,get_param))
add_param = { add_param = ParameterDefine.create_parameters({
"title": "title of event", "title": "title of event",
"start_time": "start time (UTC) of event", "start_time": "start time (UTC) of event",
"end_time": "end time (UTC) of event", "end_time": "end time (UTC) of event",
"participants": "participants of event", "participants": "participants of event",
"location": "location of event", "location": "location of event",
"details": "details of event" "details": "details of event"
} })
self.add_ai_function(SimpleAIFunction("add_event", gl.register_tool_function(SimpleAIFunction("system.calender.add_event",
"add event to calender", "add event to calender",
self._add_event,add_param)) self._add_event,add_param))
delete_param = { delete_param = ParameterDefine.create_parameters({
"event_id": "id of event" "event_id": "id of event"
} })
self.add_ai_function(SimpleAIFunction("delete_event", gl.register_tool_function(SimpleAIFunction("system.calender.delete_event",
"delete event from calender", "delete event from calender",
self._delete_event,delete_param)) self._delete_event,delete_param))
update_param = { update_param = ParameterDefine.create_parameters({
"event_id": "id of event", "event_id": "id of event",
"new_title": "new title of event", "new_title": "new title of event",
"new_participants": "new participants of event", "new_participants": "new participants of event",
@@ -79,36 +78,12 @@ class CalenderEnvironment(Environment):
"new_details": "new details of event", "new_details": "new details of event",
"start_time": "new start time (UTC) of event", "start_time": "new start time (UTC) of event",
"end_time": "new end time (UTC) of event" "end_time": "new end time (UTC) of event"
} })
self.add_ai_function(SimpleAIFunction("update_event", gl.register_tool_function(SimpleAIFunction("system.calender.update_event",
"update event in calender", "update event in calender",
self._update_event,update_param)) self._update_event,update_param))
#maybe this function should be in other env?
paint_param = {
"prompt": "A description of the content of the painting",
"model_name": "Which model to use to draw the picture, can be None"
}
self.add_ai_function(SimpleAIFunction("paint",
"Draw a picture according to the description",
self._paint,paint_param))
self.add_ai_function(SimpleAIFunction("get_contact",
"get contact info",
self._get_contact,{"name":"name of contact"}))
self.add_ai_function(SimpleAIFunction("set_contact",
"set contact info",
self._set_contact,{"name":"name of contact","contact_info":"A json to descrpit contact"}))
#self.add_ai_function(SimpleAIFunction("user_confirm",
# "user confirm",
# self._user_confirm))
async def init_db(self): async def init_db(self):
async with aiosqlite.connect(self.db_file) as db: async with aiosqlite.connect(self.db_file) as db:
await db.execute(""" await db.execute("""
@@ -151,7 +126,7 @@ class CalenderEnvironment(Environment):
_event["location"] = row[5] _event["location"] = row[5]
_event["details"] = row[6] _event["details"] = row[6]
result[row[0]] = _event result[row[0]] = _event
return json.dumps(result, indent=4, sort_keys=True) return json.dumps(result, indent=4, sort_keys=True,ensure_ascii=False)
async def _get_events_by_time_range(self,start_time, end_time): async def _get_events_by_time_range(self,start_time, end_time):
async with aiosqlite.connect(self.db_file) as db: async with aiosqlite.connect(self.db_file) as db:
@@ -177,7 +152,7 @@ class CalenderEnvironment(Environment):
if not have_result: if not have_result:
return "No event." return "No event."
return json.dumps(result, indent=4, sort_keys=True) return json.dumps(result, indent=4, sort_keys=True,ensure_ascii=False)
async def _update_event(self,event_id, new_title=None, new_participants=None, new_location=None, new_details=None ,start_time=None, end_time=None): async def _update_event(self,event_id, new_title=None, new_participants=None, new_location=None, new_details=None ,start_time=None, end_time=None):
fields_to_update = [] fields_to_update = []
@@ -239,7 +214,7 @@ class CalenderEnvironment(Environment):
cm = ContactManager.get_instance() cm = ContactManager.get_instance()
contact : Contact = cm.find_contact_by_name(name) contact : Contact = cm.find_contact_by_name(name)
if contact: if contact:
s = json.dumps(contact.to_dict()) s = json.dumps(contact.to_dict(),ensure_ascii=False)
return f"Execute get_contact OK , contact {name} is {s}" return f"Execute get_contact OK , contact {name} is {s}"
else: else:
return f"Execute get_contact OK , contact {name} not found!" return f"Execute get_contact OK , contact {name} not found!"
@@ -271,9 +246,6 @@ class CalenderEnvironment(Environment):
return f"Execute set_contact OK , contact {name} updated!" return f"Execute set_contact OK , contact {name} updated!"
async def start(self) -> None: async def start(self) -> None:
if self.is_run: if self.is_run:
return return
@@ -318,18 +290,19 @@ class CalenderEnvironment(Environment):
return f'exec paint OK, saved as a local file, path is: {result.result["file"]}' return f'exec paint OK, saved as a local file, path is: {result.result["file"]}'
class PaintEnvironment(Environment): class PaintEnvironment(SimpleEnvironment):
def __init__(self, env_id: str) -> None: def __init__(self, env_id: str) -> None:
super().__init__(env_id) super().__init__(env_id)
self.is_run = False
paint_param = {
"prompt": "Keywords of the content of the painting",
"model_name": "Which model to use to draw the picture, can be None",
"negative_prompt": "Keywords that describe what is not to be drawn, can be None" def register_functions(self):
} paint_param = ParameterDefine.create_parameters({
self.add_ai_function(SimpleAIFunction("paint", "prompt": "Description of the content of the painting",
"Draw a picture according to the keywords", })
GlobaToolsLibrary.get_instance().register_tool_function(SimpleAIFunction("aigc.text_2_image",
"Draw a picture according to the description",
self._paint,paint_param)) self._paint,paint_param))
def _do_get_value(self,key:str) -> Optional[str]: def _do_get_value(self,key:str) -> Optional[str]:
@@ -337,21 +310,22 @@ class PaintEnvironment(Environment):
async def _paint(self, prompt, model_name = None, negative_prompt = None) -> str: async def _paint(self, prompt, model_name = None, negative_prompt = None) -> str:
err, result = await ComputeKernel.get_instance().do_text_2_image(prompt, model_name, negative_prompt) result = await ComputeKernel.get_instance().do_text_2_image(prompt, model_name, negative_prompt)
if err is not None: if result.result_code == ComputeTaskResultCode.ERROR:
return f"exec paint failed. err:{err}" return f"exec paint failed. err:{result.error_str}"
else: else:
return f'exec paint OK, saved as a local file, path is: {result.result["file"]}' return f'exec paint OK, saved as a local file, path is: {result.result["file"]}'
# Default Workflow Environment(Context) # Default Workflow Environment(Context)
class WorkflowEnvironment(Environment): class WorkflowEnvironment(CompositeEnvironment):
def __init__(self, env_id: str,db_file:str) -> None: def __init__(self, env_id: str,db_file:str) -> None:
super().__init__(env_id) super().__init__(env_id)
self.db_file = db_file self.db_file = db_file
self.local = threading.local() self.local = threading.local()
self.table_name = "WorkflowEnv_" + env_id self.table_name = "WorkflowEnv_" + env_id
self.add_ai_function(TextToSpeechFunction()) # self.add_ai_function(ScriptToSpeechFunction())
# self.add_ai_function(Image2TextFunction())
def _get_conn(self): def _get_conn(self):
+328
View File
@@ -0,0 +1,328 @@
# pylint:disable=E0402
import json
import logging
import os
import aiofiles
import sqlite3
import asyncio
from typing import Any,List,Dict
import chardet
from ..proto.agent_task import *
from ..proto.ai_function import *
from ..proto.compute_task import *
from ..agent.agent_base import *
from ..storage.storage import AIStorage,ResourceLocation
from .environment import SimpleEnvironment, CompositeEnvironment
logger = logging.getLogger(__name__)
class TodoListType:
TO_WORK = "work"
TO_LEARN = "learn"
class TodoListEnvironment(SimpleEnvironment):
def __init__(self, workspace, list_type) -> None:
super().__init__(workspace)
self.root_path = os.path.join(workspace, list_type)
if not os.path.exists(self.root_path):
os.makedirs(self.root_path)
self.db_path = os.path.join(self.root_path, "todo.db")
self.conn = None
try:
self.conn = sqlite3.connect(self.db_path)
except Exception as e:
logger.error("Error occurred while connecting to database: %s", e)
return None
cursor = self.conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS todo_list (
id TEXT,
path TEXT
)
''')
self.conn.commit()
async def create_todo(params):
todoObj = AgentTodo.from_dict(params["todo"])
parent_id = params.get("parent")
return await self.create_todo(parent_id,todoObj)
self.add_ai_operation(SimpleAIAction(
op="create_todo",
description="create todo",
func_handler=create_todo,
))
async def update_todo(params):
todo_id = params["id"]
new_stat = params["state"]
return await self.update_todo(todo_id,new_stat)
self.add_ai_operation(SimpleAIAction(
op="update_todo",
description="update todo",
func_handler=update_todo,
))
def _get_todo_path(self,todo_id:str) -> str:
cursor = self.conn.cursor()
cursor.execute('''
SELECT path FROM todo_list WHERE id = ?
''',(todo_id,))
row = cursor.fetchone()
if row:
return row[0]
else:
return None
def _save_todo_path(self,todo_id:str,path:str):
cursor = self.conn.cursor()
cursor.execute('''
INSERT INTO todo_list (id,path) VALUES (?,?)
''',(todo_id,path))
self.conn.commit()
# Task/todo system , create,update,delete,query
async def get_todo_tree(self,path:str = None,deep:int = 4):
if path:
directory_path = os.path.join(self.root_path, path)
else:
directory_path = self.root_path
str_result:str = "/todos\n"
todo_count:int = 0
async def scan_dir(directory_path:str,deep:int):
nonlocal str_result
nonlocal todo_count
if deep <= 0:
return
if os.path.exists(directory_path) is False:
return
for entry in os.scandir(directory_path):
is_dir = entry.is_dir()
if not is_dir:
continue
if entry.name.startswith("."):
continue
todo_count = todo_count + 1
str_result = str_result + f"{' '*(4-deep)}{entry.name}\n"
await scan_dir(entry.path,deep-1)
await scan_dir(directory_path,deep)
return str_result,todo_count
async def get_todo_list(self,agent_id:str,path:str = None)->List[AgentTodo]:
logger.info("get_todo_list:%s,%s",agent_id,path)
if path:
directory_path = os.path.join(self.root_path, path)
else:
directory_path = self.root_path
result_list:List[AgentTodo] = []
async def scan_dir(directory_path:str,deep:int,parent:AgentTodo=None):
nonlocal result_list
if os.path.exists(directory_path) is False:
return
for entry in os.scandir(directory_path):
is_dir = entry.is_dir()
if not is_dir:
continue
if entry.name.startswith("."):
continue
todo = await self.get_todo_by_fullpath(entry.path)
if todo:
if todo.worker:
if todo.worker != agent_id:
continue
if parent:
parent.sub_todos[todo.todo_id] = todo
result_list.append(todo)
todo.rank = int(todo.create_time)>>deep
await scan_dir(entry.path,deep + 1,todo)
return
await scan_dir(directory_path,0)
#sort by rank
result_list.sort(key=lambda x:(x.rank,x.title))
logger.info("get_todo_list return,todolist.length() is %d",len(result_list))
return result_list
async def get_todo_by_fullpath(self,path:str) -> AgentTodo:
logger.info("get_todo_by_fullpath:%s",path)
detail_path = path + "/detail"
try:
with open(detail_path, mode='r', encoding="utf-8") as f:
todo_dict = json.load(f)
result_todo = AgentTodo.from_dict(todo_dict)
if result_todo:
relative_path = os.path.relpath(path, self.root_path)
if not relative_path.startswith('/'):
relative_path = '/' + relative_path
result_todo.todo_path = relative_path
else:
logger.error("get_todo_by_path:%s,parse failed!",path)
return result_todo
except Exception as e:
logger.error("get_todo_by_path:%s,failed:%s",path,e)
return None
async def create_todo(self,parent_id:str,todo:AgentTodo) -> str:
try:
if parent_id:
parent_path = self._get_todo_path(parent_id)
todo_path = f"{parent_path}/{todo.todo_id}-{todo.title}"
else:
todo_path = f"{todo.todo_id}-{todo.title}"
dir_path = f"{self.root_path}/{todo_path}"
os.makedirs(dir_path)
detail_path = f"{dir_path}/detail"
if todo.todo_path is None:
todo.todo_path = todo_path
self._save_todo_path(todo.todo_id,todo_path)
logger.info("create_todo %s",detail_path)
async with aiofiles.open(detail_path, mode='w', encoding="utf-8") as f:
await f.write(json.dumps(todo.to_dict(),ensure_ascii=False))
except Exception as e:
logger.error("create_todo failed:%s",e)
return str(e)
return None
async def update_todo(self,todo_id:str,new_stat:str)->str:
try:
todo_path = self._get_todo_path(todo_id)
full_path = f"{self.root_path}/{todo_path}"
todo : AgentTodo = await self.get_todo_by_fullpath(full_path)
if todo:
todo.state = new_stat
detail_path = f"{full_path}/detail"
async with aiofiles.open(detail_path, mode='w', encoding="utf-8") as f:
await f.write(json.dumps(todo.to_dict()),ensure_ascii=False)
return None
else:
return "todo not found."
except Exception as e:
return str(e)
async def wait_todo_done(self,todo_id:str,state=AgentTodoState.TODO_STATE_EXEC_OK) -> AgentTodo:
todo_path = self._get_todo_path(todo_id)
full_path = f"{self.root_path}/{todo_path}"
async def check_done():
while True:
todo : AgentTodo = await self.get_todo_by_fullpath(full_path)
if todo is None:
continue
if todo.state == AgentTodo.TODO_STATE_CANCEL:
break
elif todo.state == AgentTodo.TODO_STATE_EXPIRED:
break
elif todo.state == AgentTodo.TODO_STATE_WAITING_CHECK:
if state == AgentTodo.TODO_STATE_WAITING_CHECK:
break
elif todo.state == AgentTodo.TODO_STATE_DONE:
if state == AgentTodo.TODO_STATE_WAITING_CHECK:
break
elif todo.state == AgentTodo.TODO_STATE_DONE:
break
elif todo.state == AgentTodo.TODO_STATE_REVIEWED:
break
await asyncio.sleep(1)
await check_done()
return await self.get_todo_by_fullpath(full_path)
# async def append_worklog(self, todo:AgentTodo, result:AgentTodoResult):
# worklog = f"{self.root_path}/{todo.todo_path}/.worklog"
# async with aiofiles.open(worklog, mode='w+', encoding="utf-8") as f:
# content = await f.read()
# if len(content) > 0:
# json_obj = json.loads(content)
# else:
# json_obj = {}
# logs = json_obj.get("logs")
# if logs is None:
# logs = []
# logs.append(result.to_dict())
# json_obj["logs"] = logs
# await f.write(json.dumps(json_obj),ensure_ascii=False)
class WorkspaceEnvironment(CompositeEnvironment):
def __init__(self, env_id: str) -> None:
myai_path = AIStorage.get_instance().get_myai_dir()
root_path = f"{myai_path}/workspace/{env_id}"
super().__init__(root_path)
self.root_path = root_path
if not os.path.exists(self.root_path):
os.makedirs()
self.todo_list: Dict[str, TodoListEnvironment] = {}
self.todo_list[TodoListType.TO_WORK] = TodoListEnvironment(self.root_path,TodoListType.TO_WORK)
self.todo_list[TodoListType.TO_LEARN] = TodoListEnvironment(self.root_path,TodoListType.TO_LEARN)
# default environments in workspace
self.add_env(self.todo_list[TodoListType.TO_WORK])
def set_root_path(self,path:str):
self.root_path = path
def get_prompt(self) -> AgentMsg:
return None
def get_role_prompt(self,role_id:str) -> LLMPrompt:
return None
def get_do_prompt(self,todo:AgentTodo=None)->LLMPrompt:
return None
# result mean: list[op_error_str],have_error
async def exec_op_list(self,oplist:List,agent_id:str)->tuple[List[str],bool]:
result_str = "op list is none"
if oplist is None or len(oplist) == 0:
return None,False
result_str = []
have_error = False
for op in oplist:
operation = self.get_ai_operation(op["op"])
if operation:
error_str = await operation.execute(op)
else:
logger.error(f"execute op list failed: unknown op:{op['op']}")
error_str = f"execute op list failed: unknown op:{op['op']}"
if error_str:
have_error = True
result_str.append(error_str)
else:
result_str.append(f"execute success!")
return result_str,have_error
@@ -1,10 +1,11 @@
from typing import Coroutine,Dict,Any from typing import Coroutine,Dict,Any
from .agent_message import AgentMsg,AgentMsgStatus,AgentMsgType
import asyncio import asyncio
from asyncio import Queue from asyncio import Queue
import logging import logging
from ..proto.agent_msg import *
from ..agent.agent_base import *
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class AIBusHandler: class AIBusHandler:
@@ -109,6 +110,9 @@ class AIBus:
# means sub # means sub
def register_message_handler(self,handler_name:str,handler:Any) -> Queue: def register_message_handler(self,handler_name:str,handler:Any) -> Queue:
handler_node = AIBusHandler(handler,self) handler_node = AIBusHandler(handler,self)
if self.handlers.get(handler_name) is not None:
logger.warn(f"handler {handler_name} already register on AI_BUS!")
self.handlers[handler_name] = handler_node self.handlers[handler_name] = handler_node
return handler_node.queue return handler_node.queue
@@ -3,12 +3,13 @@ import random
from typing import Optional from typing import Optional
import logging import logging
import asyncio import asyncio
import tiktoken
from asyncio import Queue from asyncio import Queue
from knowledge import ObjectID from ..proto.compute_task import *
from .agent import AgentPrompt from ..knowledge import ObjectID
from .compute_node import ComputeNode from .compute_node import ComputeNode
from .compute_task import ComputeTask, ComputeTaskState, ComputeTaskResult, ComputeTaskType,ComputeTaskResultCode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -16,7 +17,6 @@ logger = logging.getLogger(__name__)
# to suitable computing nodes, achieving a balance of speed, cost, and power consumption, # to suitable computing nodes, achieving a balance of speed, cost, and power consumption,
# is the CORE GOAL of the entire computing task schedule system (aios_kernel). # is the CORE GOAL of the entire computing task schedule system (aios_kernel).
class ComputeKernel: class ComputeKernel:
_instance = None _instance = None
@classmethod @classmethod
@@ -104,16 +104,34 @@ class ComputeKernel:
def is_task_support(self, task: ComputeTask) -> bool: def is_task_support(self, task: ComputeTask) -> bool:
return True return True
@staticmethod
def llm_num_tokens_from_text(text:str,model:str) -> int:
if model is None:
model = "gpt-4-turbo-preview"
try:
encoding = tiktoken.encoding_for_model(model)
except KeyError:
logger.debug("Warning: model not found. Using cl100k_base encoding.")
encoding = tiktoken.get_encoding("cl100k_base")
token_count = len(encoding.encode(text))
return token_count
@staticmethod
def llm_num_tokens(prompt: LLMPrompt, model_name: str = None) -> int:
return ComputeKernel.llm_num_tokens_from_text(prompt.as_str(), model_name)
# friendly interface for use: # friendly interface for use:
def llm_completion(self, prompt: AgentPrompt, mode_name: Optional[str] = None, max_token: int = 0,inner_functions = None): def llm_completion(self, prompt: LLMPrompt, resp_mode:str="text",mode_name: Optional[str] = None, max_token: int = 0,inner_functions = None):
# craete a llm_work_task ,push on queue's end # craete a llm_work_task ,push on queue's end
# then task_schedule would run this task.(might schedule some work_task to another host) # then task_schedule would run this task.(might schedule some work_task to another host)
task_req = ComputeTask() task_req = ComputeTask()
task_req.set_llm_params(prompt, mode_name, max_token,inner_functions) task_req.set_llm_params(prompt,resp_mode,mode_name, max_token,inner_functions)
self.run(task_req) self.run(task_req)
return task_req return task_req
async def _wait_task(self,task_req:ComputeTask)->ComputeTaskResult: async def _wait_task(self,task_req:ComputeTask, timeout=60)->ComputeTaskResult:
async def check_timer(): async def check_timer():
check_times = 0 check_times = 0
while True: while True:
@@ -123,7 +141,7 @@ class ComputeKernel:
if task_req.state == ComputeTaskState.ERROR: if task_req.state == ComputeTaskState.ERROR:
break break
if check_times >= 120: if timeout is not None and check_times >= timeout*2:
task_req.state = ComputeTaskState.ERROR task_req.state = ComputeTaskState.ERROR
break break
@@ -137,11 +155,13 @@ class ComputeKernel:
time_out_result = ComputeTaskResult() time_out_result = ComputeTaskResult()
time_out_result.result_code = ComputeTaskResultCode.TIMEOUT time_out_result.result_code = ComputeTaskResultCode.TIMEOUT
time_out_result.set_from_task(task_req) time_out_result.set_from_task(task_req)
## craete timeout task_result task_req.result = time_out_result
return time_out_result
async def do_llm_completion(self, prompt: AgentPrompt, mode_name: Optional[str] = None, max_token: int = 0, inner_functions = None) -> str:
task_req = self.llm_completion(prompt, mode_name, max_token,inner_functions) async def do_llm_completion(self, prompt: LLMPrompt,resp_mode:str="text", mode_name: Optional[str]=None, max_token:int=0, inner_functions=None, timeout=60) -> str:
return await self._wait_task(task_req) task_req = self.llm_completion(prompt, resp_mode,mode_name, max_token,inner_functions)
return await self._wait_task(task_req, timeout)
def text_embedding(self,input:str,model_name:Optional[str] = None): def text_embedding(self,input:str,model_name:Optional[str] = None):
@@ -181,7 +201,8 @@ class ComputeKernel:
gender: Optional[str] = None, gender: Optional[str] = None,
age: Optional[str] = None, age: Optional[str] = None,
voice_name: Optional[str] = None, voice_name: Optional[str] = None,
tone: Optional[str] = None): tone: Optional[str] = None,
model_name: Optional[str] = None):
task_req = ComputeTask() task_req = ComputeTask()
task_req.params["text"] = input task_req.params["text"] = input
task_req.params["language_code"] = language_code task_req.params["language_code"] = language_code
@@ -189,6 +210,7 @@ class ComputeKernel:
task_req.params["age"] = age task_req.params["age"] = age
task_req.params["voice_name"] = voice_name task_req.params["voice_name"] = voice_name
task_req.params["tone"] = tone task_req.params["tone"] = tone
task_req.params["model_name"] = model_name
task_req.task_type = ComputeTaskType.TEXT_2_VOICE task_req.task_type = ComputeTaskType.TEXT_2_VOICE
self.run(task_req) self.run(task_req)
@@ -197,6 +219,24 @@ class ComputeKernel:
if task_req.state == ComputeTaskState.DONE: if task_req.state == ComputeTaskState.DONE:
return task_result.result return task_result.result
async def do_speech_to_text(self,
audio: str,
model: str,
prompt: Optional[str],
response_format: Optional[str]):
task_req = ComputeTask()
task_req.params["file"] = audio
task_req.params["model_name"] = model
task_req.params["prompt"] = prompt
task_req.params["response_format"] = response_format
task_req.task_type = ComputeTaskType.VOICE_2_TEXT
self.run(task_req)
task_result = await self._wait_task(task_req)
if task_req.state == ComputeTaskState.DONE:
return task_result
def text_2_image(self, prompt:str, model_name:Optional[str] = None, negative_prompt = None): def text_2_image(self, prompt:str, model_name:Optional[str] = None, negative_prompt = None):
task = ComputeTask() task = ComputeTask()
@@ -206,11 +246,19 @@ class ComputeKernel:
async def do_text_2_image(self, prompt:str, model_name:Optional[str] = None, negative_prompt = None) -> ComputeTaskResult: async def do_text_2_image(self, prompt:str, model_name:Optional[str] = None, negative_prompt = None) -> ComputeTaskResult:
task = self.text_2_image(prompt,model_name, negative_prompt) task = self.text_2_image(prompt,model_name, negative_prompt)
task = await self._wait_task(task) task_result = await self._wait_task(task)
return task.result return task_result
# if task_req.state == ComputeTaskState.DONE: # if task_req.state == ComputeTaskState.DONE:
# return None, task_result # return None, task_result
# return task_req.error_str, None def image_2_text(self, image_path: str, prompt:str, model_name:Optional[str] = None, negative_prompt = None):
task = ComputeTask()
task.set_image_2_text_params(image_path,prompt,model_name, negative_prompt)
self.run(task)
return task
async def do_image_2_text(self, image_path: str, prompt:str, model_name:Optional[str] = None, negative_prompt = None) -> ComputeTaskResult:
task = self.image_2_text(image_path,prompt, model_name, negative_prompt)
task = await self._wait_task(task)
return task.result
@@ -1,6 +1,6 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from .compute_task import ComputeTask, ComputeTaskType
from ..proto.compute_task import ComputeTask, ComputeTaskType
class ComputeNode(ABC): class ComputeNode(ABC):
def __init__(self) -> None: def __init__(self) -> None:
+76
View File
@@ -0,0 +1,76 @@
from typing import List
import logging
from datetime import datetime
from ..proto.agent_msg import AgentMsg
from .tunnel import AgentTunnel
logger = logging.getLogger(__name__)
class Contact:
def __init__(self, name, phone=None, email=None, telegram=None,added_by=None, tags=[], notes=""):
self.name = name
self.phone = phone
self.email = email
self.telegram = telegram
self.added_by = added_by
self.tags = tags
self.notes = notes
self.is_family_member = False
self.active_tunnels = {}
self.relationship = "friends"
def to_dict(self):
return {
"name": self.name,
"phone": self.phone,
"email": self.email,
"telegram" : self.telegram,
"is_family_member": self.is_family_member,
"added_by": self.added_by,
"tags": self.tags,
"notes": self.notes,
"now" : datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
"relationship" : self.relationship
}
async def _process_msg(self,msg:AgentMsg):
tunnel : AgentTunnel = self.get_active_tunnel(msg.sender)
if tunnel is not None:
await tunnel.post_message(msg)
return None
else:
tunnel = await self.create_default_tunnel(msg.sender)
if tunnel is not None:
self.active_tunnels[msg.sender] = tunnel
await tunnel.post_message(msg)
return None
logger.warn(f"contact {self.name} cann't get tunnel,post message failed!")
def get_active_tunnel(self,agent_id) -> AgentTunnel:
tunnel = self.active_tunnels.get(agent_id)
return tunnel
def set_active_tunnel(self,agent_id,tunnel:AgentTunnel):
self.active_tunnels[agent_id] = tunnel
async def create_default_tunnel(self,agent_id:str) -> AgentTunnel:
#TODO:fix this
from .email_tunnel import EmailTunnel
result_tunnels = AgentTunnel.get_tunnel_by_agentid(agent_id)
for tunnel in result_tunnels:
if isinstance(tunnel,EmailTunnel):
return tunnel
return None
@classmethod
def from_dict(cls, data):
result = Contact(data.get("name"), data.get("phone"), data.get("email"), data.get("telegram"),data.get("added_by"), data.get("tags"), data.get("notes"))
if data.get("relationship") is not None:
result.relationship = data.get("relationship")
return result
@@ -1,48 +1,18 @@
from typing import List from typing import List
import toml import toml
import time
import logging
class Contact: from datetime import datetime
def __init__(self, name, phone=None, email=None, telegram=None,added_by=None, tags=[], notes=""): from ..proto.agent_msg import AgentMsg
self.name = name from ..proto.ai_function import ParameterDefine, SimpleAIFunction
self.phone = phone from ..agent.llm_context import GlobaToolsLibrary
self.email = email from .tunnel import AgentTunnel
self.telegram = telegram from .contact import Contact
self.added_by = added_by
self.tags = tags
self.notes = notes
self.is_family_member = False
def to_dict(self):
return {
"name": self.name,
"phone": self.phone,
"email": self.email,
"telegram" : self.telegram,
"added_by": self.added_by, logger = logging.getLogger(__name__)
"tags": self.tags,
"notes": self.notes
}
@classmethod
def from_dict(cls, data):
return Contact(data.get("name"), data.get("phone"), data.get("email"), data.get("telegram"),data.get("added_by"), data.get("tags"), data.get("notes"))
class FamilyMember(Contact):
def __init__(self, name, relationship,phone=None, email=None,telegram=None):
super().__init__(name, phone, email, telegram)
self.name = name
self.relationship = relationship
self.is_family_member = True
def to_dict(self):
result = super().to_dict()
result["relationship"] = self.relationship
return result
@classmethod
def from_dict(cls, data):
return FamilyMember(data.get("name"),data.get("relationship"),data.get("phone"), data.get("email"),data.get("telegram"))
class ContactManager: class ContactManager:
_instance = None _instance = None
@@ -52,10 +22,27 @@ class ContactManager:
cls._instance = ContactManager(str(filename)) cls._instance = ContactManager(str(filename))
return cls._instance return cls._instance
def register_global_functions(self):
gl = GlobaToolsLibrary.get_instance()
get_parameters = ParameterDefine.create_parameters({"name":"contact name name"})
gl.register_tool_function(SimpleAIFunction("system.contacts.get",
"get contact info",
self._get_contact,get_parameters))
# todo: use json to save contact info
update_parameters = ParameterDefine.create_parameters({"name":"name","contact_info":"A json to descrpit contact"})
gl.register_tool_function(SimpleAIFunction("system.contacts.set",
"set contact info",
self._set_contact,update_parameters))
return
def __init__(self, filename="contacts.toml"): def __init__(self, filename="contacts.toml"):
self.filename = filename self.filename = filename
self.contacts = [] self.contacts = []
self.family_members = []
self.is_auto_create_contact_from_telegram = True self.is_auto_create_contact_from_telegram = True
@@ -69,12 +56,10 @@ class ContactManager:
def load_from_config(self,config_data:dict): def load_from_config(self,config_data:dict):
self.contacts = [Contact.from_dict(item) for item in config_data.get("contacts", [])] self.contacts = [Contact.from_dict(item) for item in config_data.get("contacts", [])]
self.family_members = [FamilyMember.from_dict(item) for item in config_data.get("family_members", [])]
def save_data(self): def save_data(self):
data = { data = {
"contacts": [contact.to_dict() for contact in self.contacts], "contacts": [contact.to_dict() for contact in self.contacts],
"family_members": [member.to_dict() for member in self.family_members]
} }
with open(self.filename, "w") as f: with open(self.filename, "w") as f:
toml.dump(data, f) toml.dump(data, f)
@@ -108,46 +93,28 @@ class ContactManager:
if contact.name == name: if contact.name == name:
return contact return contact
for member in self.family_members:
if member.name == name:
return member
return None return None
def find_contact_by_telegram(self, telegram:str): def find_contact_by_telegram(self, telegram:str):
for contact in self.contacts: for contact in self.contacts:
if contact.telegram == telegram: if contact.telegram == telegram:
return contact return contact
for member in self.family_members:
if member.telegram == telegram:
return member
return None return None
def find_contact_by_email(self, email:str): def find_contact_by_email(self, email:str):
for contact in self.contacts: for contact in self.contacts:
if contact.email == email: if contact.email == email:
return contact return contact
for member in self.family_members:
if member.email == email:
return member
return None return None
def find_contact_by_phone(self, phone:str): def find_contact_by_phone(self, phone:str):
for contact in self.contacts: for contact in self.contacts:
if contact.phone == phone: if contact.phone == phone:
return contact return contact
for member in self.family_members:
if member.phone == phone:
return member
return None return None
def add_family_member(self, name, new_member:FamilyMember):
assert name == new_member.name
self.family_members.append(new_member)
self.save_data()
def list_contacts(self): def list_contacts(self):
return self.contacts return self.contacts
def list_family_members(self):
return self.family_members
@@ -4,8 +4,7 @@ from asyncio import Queue
import logging import logging
from abc import abstractmethod from abc import abstractmethod
from .compute_task import ComputeTask, ComputeTaskResult, ComputeTaskResultCode, ComputeTaskState, ComputeTaskType from aios import ComputeTask, ComputeNode,ComputeTaskResult, ComputeTaskResultCode, ComputeTaskState, ComputeTaskType
from .compute_node import ComputeNode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -1,7 +1,8 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import logging import logging
from typing import Coroutine from typing import Coroutine
from .agent_message import AgentMsg
from ..proto.agent_msg import AgentMsg
from .bus import AIBus from .bus import AIBus
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -54,13 +55,21 @@ class AgentTunnel(ABC):
return False return False
@classmethod
async def get_tunnel_by_agentid(cls,agent_id:str):
result = []
for tunnel in cls._all_tunnels.values():
if tunnel.target_id == agent_id:
result.append(tunnel)
return result
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self.tunnel_id = None self.tunnel_id = None
self.target_id = None self.target_id = None
self.target_type = None self.target_type = None
self.ai_bus = None self.ai_bus: AIBus = None
self.is_connected = False self.is_connected = False
def connect_to(self, ai_bus:AIBus,target_id: str) -> None: def connect_to(self, ai_bus:AIBus,target_id: str) -> None:
@@ -75,6 +84,9 @@ class AgentTunnel(ABC):
self.ai_bus = ai_bus self.ai_bus = ai_bus
self.is_connected = True self.is_connected = True
@abstractmethod
def post_message(self, msg: AgentMsg) -> None:
pass
@abstractmethod @abstractmethod
@@ -3,3 +3,4 @@ from .vector import *
from .data import * from .data import *
from .store import KnowledgeStore from .store import KnowledgeStore
from .core_object import * from .core_object import *
from .pipeline import *
@@ -1,7 +1,6 @@
from ..object import KnowledgeObject, ObjectRelationStore from ..object import KnowledgeObject, ObjectRelationStore
from ..data import ChunkList, ChunkListWriter from ..data import ChunkList, ChunkListWriter
from ..object import ObjectType from ..object import ObjectType
from .. import KnowledgeStore
# desc # desc
# meta # meta
@@ -49,13 +48,13 @@ class DocumentObjectBuilder:
self.text = text self.text = text
return self return self
def build(self) -> DocumentObject: def build(self, store) -> DocumentObject:
chunk_list = KnowledgeStore().get_chunk_list_writer().create_chunk_list_from_text(self.text) chunk_list = store.get_chunk_list_writer().create_chunk_list_from_text(self.text)
doc = DocumentObject(self.meta, self.tags, chunk_list) doc = DocumentObject(self.meta, self.tags, chunk_list)
doc_id = doc.calculate_id() doc_id = doc.calculate_id()
# Add relation to store # Add relation to store
for chunk_id in chunk_list.chunk_list: for chunk_id in chunk_list.chunk_list:
KnowledgeStore().get_relation_store().add_relation(chunk_id, doc_id) store.get_relation_store().add_relation(chunk_id, doc_id)
return doc return doc
@@ -1,4 +1,3 @@
from .. import KnowledgeStore
from .rich_text_object import RichTextObject, RichTextObjectBuilder from .rich_text_object import RichTextObject, RichTextObjectBuilder
from ..object import ObjectID, ObjectType, KnowledgeObject from ..object import ObjectID, ObjectType, KnowledgeObject
from .document_object import DocumentObjectBuilder from .document_object import DocumentObjectBuilder
@@ -68,11 +67,11 @@ class EmailObjectBuilder:
self.folder = folder self.folder = folder
return self return self
def build(self) -> EmailObject: def build(self, store) -> EmailObject:
# Just get the object store and relation store from global KnowledgeStore # Just get the object store and relation store from global KnowledgeStore
store = KnowledgeStore().get_object_store() store = store.get_object_store()
relation = KnowledgeStore().get_relation_store() relation = store.get_relation_store()
# Read meta.json # Read meta.json
meta = {} meta = {}
@@ -1,7 +1,6 @@
from ..object import KnowledgeObject from ..object import KnowledgeObject
from ..data import ChunkList, ChunkListWriter from ..data import ChunkList, ChunkListWriter
from ..object import ObjectType from ..object import ObjectType
from .. import KnowledgeStore
import os import os
# desc # desc
@@ -86,10 +85,10 @@ class ImageObjectBuilder:
self.restore_file = restore_file self.restore_file = restore_file
return self return self
def build(self) -> ImageObject: def build(self, store) -> ImageObject:
file_size = os.path.getsize(self.image_file) file_size = os.path.getsize(self.image_file)
chunk_list = KnowledgeStore().get_chunk_list_writer().create_chunk_list_from_file( chunk_list = store.get_chunk_list_writer().create_chunk_list_from_file(
self.image_file, 1024 * 1024 * 4, self.restore_file self.image_file, 1024 * 1024 * 4, self.restore_file
) )
exif = get_exif_data(self.image_file) exif = get_exif_data(self.image_file)
@@ -1,4 +1,4 @@
from knowledge.object.object_id import ObjectType from ..object.object_id import ObjectType
from ..object import KnowledgeObject from ..object import KnowledgeObject
from ..data import ChunkList, ChunkListWriter from ..data import ChunkList, ChunkListWriter
from ..object import ObjectType from ..object import ObjectType
@@ -1,7 +1,6 @@
from ..object import KnowledgeObject from ..object import KnowledgeObject
from ..data import ChunkList, ChunkListWriter from ..data import ChunkList, ChunkListWriter
from ..object import ObjectType from ..object import ObjectType
from .. import KnowledgeStore
# desc # desc
# meta # meta
@@ -76,8 +75,8 @@ class VideoObjectBuilder:
self.restore_file = restore_file self.restore_file = restore_file
return self return self
def build(self) -> VideoObject: def build(self, store) -> VideoObject:
chunk_list = KnowledgeStore().get_chunk_list_writer().create_chunk_list_from_file( chunk_list = store.get_chunk_list_writer().create_chunk_list_from_file(
self.video_file, 1024 * 1024 * 4, self.restore_file self.video_file, 1024 * 1024 * 4, self.restore_file
) )
info = get_video_info(self.video_file) info = get_video_info(self.video_file)
@@ -86,7 +86,7 @@ def _split_text_with_regex(
return [s for s in splits if s != ""] return [s for s in splits if s != ""]
def _split_text( def split_text(
text: str, text: str,
separators: List[str], separators: List[str],
chunk_size: int, chunk_size: int,
@@ -127,7 +127,7 @@ def _split_text(
if not new_separators: if not new_separators:
final_chunks.append(s) final_chunks.append(s)
else: else:
other_info = _split_text(s, new_separators, chunk_size, chunk_overlap, length_function) other_info = split_text(s, new_separators, chunk_size, chunk_overlap, length_function)
final_chunks.extend(other_info) final_chunks.extend(other_info)
if _good_splits: if _good_splits:
merged_text = _merge_splits(_good_splits, _separator, chunk_size, chunk_overlap, length_function) merged_text = _merge_splits(_good_splits, _separator, chunk_size, chunk_overlap, length_function)
@@ -197,7 +197,7 @@ class ChunkListWriter:
) )
) )
text_list = _split_text(text, separators, chunk_size, chunk_overlap, length_function) text_list = split_text(text, separators, chunk_size, chunk_overlap, length_function)
chunk_list = [] chunk_list = []
hash_obj = hashlib.sha256() hash_obj = hashlib.sha256()
@@ -48,10 +48,22 @@ class KnowledgeObject(ABC):
def get_body(self) -> dict: def get_body(self) -> dict:
return self.body return self.body
def get_summary(self) -> str:
return self.desc.get("summary")
# def get_articl_catelog(self) -> str:
# assert self.object_type == ObjectType.Document
# return self.desc.get("catelog")
# def get_article_full_content(self) -> str:
# assert self.object_type == ObjectType.Document
# return self.body
def calculate_id(self): def calculate_id(self):
# Convert the object_type and desc to string and compute the SHA256 hash # Convert the object_type and desc to string and compute the SHA256 hash
data = json.dumps( data = json.dumps(
{"object_type": self.object_type, "desc": self.desc}, {"object_type": self.object_type, "desc": self.desc},
ensure_ascii=False,
cls=ObjectEnhancedJSONEncoder, cls=ObjectEnhancedJSONEncoder,
) )
sha256 = hashlib.sha256() sha256 = hashlib.sha256()
@@ -63,5 +75,5 @@ class KnowledgeObject(ABC):
return pickle.dumps(self) return pickle.dumps(self)
@staticmethod @staticmethod
def decode(data: bytes) -> "ImageObject": def decode(data: bytes) -> "KnowledgeObject":
return pickle.loads(data) return pickle.loads(data)
@@ -13,6 +13,17 @@ class ObjectType(IntEnum):
Document = 103 Document = 103
RichText = 104 RichText = 104
Email = 105 Email = 105
UserDef = 200
def is_user_def(self) -> bool:
return self.value >= 200
def get_user_def_type_code(self):
return (self.value - 200) if self.is_user_def() else None
@classmethod
def from_user_def_type_code(cls, value):
return value + 200
# define a object ID class to identify a object # define a object ID class to identify a object
@@ -56,3 +67,6 @@ class ObjectID: # pylint: disable=too-few-public-methods
def __eq__(self, other) -> bool: def __eq__(self, other) -> bool:
return self.value == other.value return self.value == other.value
def __hash__(self):
return hash(self.value)

Some files were not shown because too many files have changed in this diff Show More