feat: V1.0 (#734)

Co-authored-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: Wendy Tang <wendytang@squareup.com>
Co-authored-by: Jarrod Sibbison <72240382+jsibbison-square@users.noreply.github.com>
Co-authored-by: Alex Hancock <alex.hancock@example.com>
Co-authored-by: Alex Hancock <alexhancock@block.xyz>
Co-authored-by: Lifei Zhou <lifei@squareup.com>
Co-authored-by: Wes <141185334+wesrblock@users.noreply.github.com>
Co-authored-by: Max Novich <maksymstepanenko1990@gmail.com>
Co-authored-by: Zaki Ali <zaki@squareup.com>
Co-authored-by: Salman Mohammed <smohammed@squareup.com>
Co-authored-by: Kalvin C <kalvinnchau@users.noreply.github.com>
Co-authored-by: Alec Thomas <alec@swapoff.org>
Co-authored-by: lily-de <119957291+lily-de@users.noreply.github.com>
Co-authored-by: kalvinnchau <kalvin@block.xyz>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Rizel Scarlett <rizel@squareup.com>
Co-authored-by: bwrage <bwrage@squareup.com>
Co-authored-by: Kalvin Chau <kalvin@squareup.com>
Co-authored-by: Alice Hau <110418948+ahau-square@users.noreply.github.com>
Co-authored-by: Alistair Gray <ajgray@stripe.com>
Co-authored-by: Nahiyan Khan <nahiyan.khan@gmail.com>
Co-authored-by: Alex Hancock <alexhancock@squareup.com>
Co-authored-by: Nahiyan Khan <nahiyan@squareup.com>
Co-authored-by: marcelle <1852848+laanak08@users.noreply.github.com>
Co-authored-by: Yingjie He <yingjiehe@block.xyz>
Co-authored-by: Yingjie He <yingjiehe@squareup.com>
Co-authored-by: Lily Delalande <ldelalande@block.xyz>
Co-authored-by: Adewale Abati <acekyd01@gmail.com>
Co-authored-by: Ebony Louis <ebony774@gmail.com>
Co-authored-by: Angie Jones <jones.angie@gmail.com>
Co-authored-by: Ebony Louis <55366651+EbonyLouis@users.noreply.github.com>
This commit is contained in:
Bradley Axen
2025-01-24 13:04:43 -08:00
committed by GitHub
parent eccb1b2261
commit 1c9a7c0b05
688 changed files with 71147 additions and 19132 deletions
+68 -57
View File
@@ -1,12 +1,12 @@
# Architecture
## The System
## The Extension System
Goose extends the capabilities of high-performing LLMs through a small collection of tools.
This lets you instruct goose, currently via a CLI interface, to automatically solve problems
on your behalf. It attempts to not just tell you how you can do something, but to actually do it for you.
The primary mode of goose (the "developer" toolkit) has access to tools to
The primary mode of goose (the "developer" extension) has access to tools to
- maintain a plan
- run shell commands
@@ -40,7 +40,7 @@ that you should be able to observe by using it.
## Implementation
The core execution logic for generation and tool calling is handled by [exchange][exchange].
It hooks python functions into the model tool use loop, while defining very careful error handling
It hooks rust functions into the model tool use loop, while defining very careful error handling
so any failures in tools are surfaced to the model.
Once we've created an *exchange* object, running the process is effectively just calling
@@ -50,7 +50,7 @@ Once we've created an *exchange* object, running the process is effectively just
Goose builds that exchange:
- allows users to configure a profile to customize capabilities
- provides a pluggable system for adding tools and prompts
- provides a pluggable extension system for adding tools and prompts
- sets up the tools to interact with state
We expect that goose will have multiple UXs over time, and be run in different
@@ -60,28 +60,29 @@ notifications on stdout).
Goose then constructs the exchange for the UX, the UX only interacts with that exchange.
```
def build_exchange(profile: Profile, notifier: Notifier) -> Exchange:
```rust
fn build_exchange(profile: Profile, notifier: Notifier) -> Exchange {
...
}
```
But to setup a configurable system, Goose uses `Toolkit`s:
But to setup a configurable system, Goose uses `Extensions`:
```
(Profile, Notifier) -> [Toolkits] -> Exchange
(Profile, Notifier) -> [Extensions] -> Exchange
```
## Profile
A profile specifies some basic configuration in Goose, such as which models it should use, as well
as which toolkits it should include.
as which extensions it should include.
```yaml
processor: openai:gpt-4o
accelerator: openai:gpt-4o-mini
processor: openai:gpt-4
accelerator: openai:gpt-4-turbo
moderator: passive
toolkits:
- assistant
extensions:
- developer
- calendar
- contacts
- name: scheduling
@@ -93,16 +94,14 @@ toolkits:
## Notifier
The notifier is a concrete implementation of the Notifier base class provided by each UX. It
needs to support two methods
The notifier is a concrete implementation of the Notifier trait provided by each UX. It
needs to support two methods:
```python
class Notifier:
def log(self, RichRenderable):
...
def status(self, str):
...
```rust
trait Notifier {
fn log(&self, content: RichRenderable);
fn status(&self, message: String);
}
```
Log is meant to record something concrete that happened, such as a tool being called, and status is intended
@@ -110,57 +109,69 @@ for transient displays of the current status. For example, while a shell command
`.log` to record the command that started, and then update the status to `"shell command running"`. Log is durable
while Status is ephemeral.
## Toolkits
## Extensions
Toolkits are a collection of tools, along with the state and prompting they require.
Toolkits are what gives Goose its capabilities.
Extensions are a collection of tools, along with the state and prompting they require.
Extensions are what gives Goose its capabilities.
Tools need a way to report what's happening back to the user, which we treat similarly
to logging. To make that possible, toolkits get a reference to the interface described above.
to logging. To make that possible, extensions get a reference to the interface described above.
```python
class ScheduleToolkit(Toolkit):
def __init__(self, notifier: Notifier, requires: Requirements, **kwargs):
super().__init__(notifier, requires, **kwargs) # handles the interface, exchangeview
# for a class that has requirements, you can get them like this
self.calendar = requires.get("calendar")
self.assistant = requires.get("assistant")
self.contacts = requires.get("contacts")
self.appointments_state = []
```rust
struct ScheduleExtension {
notifier: Box<dyn Notifier>,
calendar: Box<dyn Calendar>,
assistant: Box<dyn Assistant>,
contacts: Box<dyn Contacts>,
appointments_state: Vec<Appointment>,
}
def prompt(self) -> str:
return "Try out the example tool."
impl Extension for ScheduleExtension {
fn new(notifier: Box<dyn Notifier>, requires: Requirements) -> Self {
Self {
notifier,
calendar: requires.get("calendar"),
assistant: requires.get("assistant"),
contacts: requires.get("contacts"),
appointments_state: vec![],
}
}
@tool
def example(self):
self.interface.log(f"An example tool was called, current state is {self.state}")
fn prompt(&self) -> String {
"Try out the example tool.".to_string()
}
#[tool]
fn example(&self) {
self.notifier.log(format!("An example tool was called, current state is {:?}", self.appointments_state));
}
}
```
### Advanced
**Dependencies**: Toolkits can depend on each other, to make it easier to get plugins to extend
or modify existing capabilities. In the config above, you can see this used for the scheduling toolkit.
**Dependencies**: Extensions can depend on each other, to make it easier to get plugins to extend
or modify existing capabilities. In the config above, you can see this used for the scheduling extension.
You can refer to those requirements in code through:
```python
@tool
def example_dependency(self):
appointments = self.dependencies["calendar"].appointments
...
```rust
#[tool]
fn example_dependency(&self) {
let appointments = self.calendar.appointments();
// ...
}
```
**ExchangeView**: It can also be useful for tools to have a read-only copy of the history
of the loop so far. So for advanced use cases, toolkits also have access to an
of the loop so far. So for advanced use cases, extensions also have access to an
`ExchangeView` object.
```python
@tool
def example_history(self):
last_message = self.exchange_view.processor.messages[-1]
...
```rust
#[tool]
fn example_history(&self) {
let last_message = self.exchange_view.processor.messages.last();
// ...
}
```
[exchange]: https://github.com/block/goose/tree/main/packages/exchange
[exchange]: https://github.com/block/goose/tree/main/packages/exchange