add more architecture docs
This commit is contained in:
53
architecture/agents.md
Normal file
53
architecture/agents.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Agents
|
||||
|
||||
Agents can be viewed as an FSM using an LLM to generate inputs into the system that operates over a DAG.
|
||||
|
||||
What this really means is that the agent is just a function without memory that uses text inputs and outputs in a
|
||||
defined order.
|
||||
|
||||
```python
|
||||
def my_agent(*args, **kwargs) -> str:
|
||||
# do whatever you want!
|
||||
return "Hi I'm an agent!"
|
||||
```
|
||||
|
||||
Now obviously, that's like saying water's wet, but we're going to be using that definition to inform our design of the
|
||||
library, namely, that we should *not* store agent state outside the function call.
|
||||
|
||||
## The Agent Class
|
||||
|
||||
So we don't have state, why are we using a class?
|
||||
|
||||
Well, we want to initialize things, we want to have some configuration, and we want to have some helper functions.
|
||||
Preferably all in a single place.
|
||||
|
||||
```python
|
||||
class BaseAgent:
|
||||
def agent_primitives(self) -> list[BaseAgent]:
|
||||
# Returns a list of Agents that are utilized by this agent to generate inputs
|
||||
raise NotImplementedError
|
||||
|
||||
def tools(self) -> list[BaseTool]:
|
||||
# Returns a list of tools that the agent needs to run
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def run(self, config, *args, **kwargs) -> ConversationGraph:
|
||||
llm = get_llm(config)
|
||||
tools = self.tools()
|
||||
for agent in self.agent_primitives():
|
||||
tools.extend(agent.tools())
|
||||
tools = set(tools)
|
||||
tools = initialize_tools(tools, config)
|
||||
return self(llm, tools, config, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def __call__(self, llm, tools, config, *args, **kwargs) -> ConversationGraph:
|
||||
# Returns a ConversationGraph that can be parsed to get the output of the agent
|
||||
# Use w/e args/kwargs you want, as long as llm/tools/config are satisfied.
|
||||
raise NotImplementedError
|
||||
```
|
||||
|
||||
Doesn't seem too bad (I hope), it is a bit annoying that we don't initialize everything in the constructor, but
|
||||
hopefully we all kinda like it :)
|
||||
|
||||
14
architecture/llm_client.md
Normal file
14
architecture/llm_client.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# LLM Client
|
||||
|
||||
A quick wrapper over openai apis
|
||||
|
||||
## Responsibilities
|
||||
|
||||
- Transform "normal" chat/completions requests into graphs
|
||||
- Translate graphs into LLM requests
|
||||
- Keep a history of graphs parsed by it
|
||||
- On Policy Data
|
||||
- Deduplicating graphs, so we don't keep previous history as separate graphs
|
||||
|
||||
## How to use
|
||||
Exactly the same as the openai api! Just with the additional support of graph inputs and outputs.
|
||||
@@ -45,7 +45,7 @@ graph TD
|
||||
class ChatMetadata,ChatResponseText,ChatContent,ToolMetadata,ToolChat,ToolContent,ToolMetadataLength metadataNode
|
||||
```
|
||||
|
||||
Messages should be a graph of immutable elements.
|
||||
Messages should be a graph (DAG, specifically) of immutable elements.
|
||||
|
||||
## Why immutable elements?
|
||||
We want to train on policy
|
||||
@@ -54,9 +54,60 @@ We want to train on policy
|
||||
## Why a graph?
|
||||
Nodes and connections are a natural way to represent the flow of information in an agent conversation.
|
||||
|
||||
|
||||
## Will this be annoying to deal with?
|
||||
|
||||
It shouldn't be! While there will be internal stuff that may look ???, for the interface, it should be as simple as your
|
||||
normal context window edits, so `message_history[2]['content'] = my_edit`, but internally we'll deal with the recordkeeping
|
||||
and how this ends up parsing into on policy training data, if requested.
|
||||
and how this ends up parsing into on policy training data, if requested.
|
||||
|
||||
## Edges
|
||||
|
||||
Edges are the connections between nodes, and there are two types we are concerned with:
|
||||
- **Sequential edges**: These represent the flow of conversation, connecting messages in the order they were sent. For example, a user message followed by an assistant response.
|
||||
- **Parallel edges**: These represent versioning, e.g. edit history, context squishing, etc.
|
||||
|
||||
## So what does this look like in practice?
|
||||
|
||||
```python
|
||||
import copy
|
||||
|
||||
|
||||
class MessageGraph:
|
||||
def __init__(self):
|
||||
self.messages = []
|
||||
self.prev_graph = None
|
||||
|
||||
def append(self, message):
|
||||
self.messages.append(message)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.messages[index]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
# check if an assistant message is after this indx
|
||||
needs_new_graph = False
|
||||
first_idx = -1
|
||||
for i in range(key, len(self.messages)):
|
||||
if (i == key) and (value['role'] == 'assistant') and (value['content'] == self.messages[i]['content']):
|
||||
# no op
|
||||
return
|
||||
needs_new_graph = needs_new_graph or (self.messages[i]['role'] == 'assistant')
|
||||
if needs_new_graph and first_idx == -1:
|
||||
first_idx = i
|
||||
if needs_new_graph:
|
||||
self.prev_graph = copy.deepcopy(self)
|
||||
self.messages[key] = value
|
||||
|
||||
def __len__(self):
|
||||
return len(self.messages)
|
||||
|
||||
def __eq__(self, other):
|
||||
return "\n\n".join(f"{msg['role']}: {msg['content']}" for msg in self) == "\n\n".join(
|
||||
f"{msg['role']}: {msg['content']}" for msg in other)
|
||||
|
||||
|
||||
# in use
|
||||
messages = MessageGraph()
|
||||
messages.append({'role': 'system', 'content': 'Hello, I am a system message'})
|
||||
messages[0] = {'role': 'user', 'content': 'Hello, I am a user message'}
|
||||
```
|
||||
16
architecture/tools.md
Normal file
16
architecture/tools.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Tools
|
||||
|
||||
Not much on this, yet. Tools are just a stateful wrapper around a function, so we can do things like:
|
||||
- Keep a docker container running
|
||||
- Keep a game online
|
||||
|
||||
```python
|
||||
class BaseTool:
|
||||
def definitions(self) -> List[Dict[str, Any]]:
|
||||
# OpenAI API compatible definitions
|
||||
raise NotImplementedError
|
||||
|
||||
def __call__(self, *args, **kwargs) -> Dict[str, Any]:
|
||||
# Returns at minimum {'role': 'tool', 'content': '...'}
|
||||
raise NotImplementedError
|
||||
```
|
||||
@@ -1,3 +1,6 @@
|
||||
firecrawl-py
|
||||
openai
|
||||
fal-client
|
||||
fal-client
|
||||
fire
|
||||
git@github.com:NousResearch/hecate.git
|
||||
tenacity
|
||||
Reference in New Issue
Block a user