Skip to content

Commit

Permalink
feat: make goosehints jinja templated (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
salman1993 authored Sep 4, 2024
1 parent 7d5461d commit 72d927f
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 20 deletions.
46 changes: 32 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ goose
</p>

<p align="center">
<a href="#usage">Usage</a> •
<a href="#usage">Usage</a> •
<a href="#configuration">Configuration</a> •
<a href="#tips">Tips</a> •
<a href="#faq">FAQ</a> •
Expand All @@ -25,7 +25,7 @@ To solve problems, `goose` breaks down instructions into sequences of tasks and


## Usage
### Installation
### Installation

To install `goose`, we recommend `pipx`

Expand All @@ -36,7 +36,7 @@ brew install pipx
pipx ensurepath
```

Then you can install `goose` with
Then you can install `goose` with

```sh
pipx install goose-ai
Expand All @@ -45,7 +45,7 @@ pipx install goose-ai
There is an early version of a VS Code extension with goose support you can try here: https://github.com/square/goose-vscode - more to come soon.

### LLM provider access setup
`goose` works on top of LLMs (you need to bring your own LLM). By default, `goose` uses `openai` as LLM provider. You need to set OPENAI_API_KEY as an environment variable if you would like to use `openai`.
`goose` works on top of LLMs (you need to bring your own LLM). By default, `goose` uses `openai` as LLM provider. You need to set OPENAI_API_KEY as an environment variable if you would like to use `openai`.
```sh
export OPENAI_API_KEY=your_open_api_key
```
Expand Down Expand Up @@ -80,10 +80,10 @@ goose session resume

## Configuration

`goose` can detect what LLM and toolkits it can work with from the configuration file `~/.config/goose/profiles.yaml` automatically.
`goose` can detect what LLM and toolkits it can work with from the configuration file `~/.config/goose/profiles.yaml` automatically.

### Configuration options
Example:
Example:

```yaml
default:
Expand Down Expand Up @@ -124,12 +124,30 @@ Rules designed to control or manage the output of the model. Moderators that cur

#### toolkits

`goose` can be extended with toolkits, and out of the box there are some available:
`goose` can be extended with toolkits, and out of the box there are some available:

* `developer`: for general-purpose development capabilities, including plan management, shell execution, and file operations, with default shell strategies like using ripgrep.
* `screen`: for letting goose take a look at your screen to help debug or work on designs (gives goose eyes)
* `github`: for awareness and suggestions on how to use github
* `repo_context`: for summarizing and understanding a repository you are working in.

#### Configuring goose per repo

If you are using the `developer` toolkit, `goose` adds the content from `.goosehints`
file in working directory to the system prompt of the `developer` toolkit. The hints
file is meant to provide additional context about your project. The context can be
user-specific or at the project level in which case, you
can commit it to git. `.goosehints` file is Jinja templated so you could have something
like this:
```
Here is an overview of how to contribute:
{% include 'CONTRIBUTING.md' %}
The following justfile shows our common commands:
```just
{% include 'justfile' %}
```

### Examples
#### provider as `anthropic`

Expand Down Expand Up @@ -158,33 +176,33 @@ Here are some collected tips we have for working efficiently with `goose`

- **`goose` can and will edit files**. Use a git strategy to avoid losing anything - such as staging your
personal edits and leaving `goose` edits unstaged until reviewed. Or consider using individual commits which can be reverted.
- **`goose` can and will run commands**. You can ask it to check with you first if you are concerned. It will check commands for safety as well.
- **`goose` can and will run commands**. You can ask it to check with you first if you are concerned. It will check commands for safety as well.
- You can interrupt `goose` with `CTRL+C` to correct it or give it more info.
- `goose` works best when solving concrete problems - experiment with how far you need to break that problem
down to get `goose` to solve it. Be specific! E.g. it will likely fail to `"create a banking app"`,
down to get `goose` to solve it. Be specific! E.g. it will likely fail to `"create a banking app"`,
but probably does a good job if prompted with `"create a Fastapi app with an endpoint for deposit and withdrawal
and with account balances stored in mysql keyed by id"`
- If `goose` doesn't have enough context to start with, it might go down the wrong direction. Tell it
to read files that you are referring to or search for objects in code. Even better, ask it to summarize
them for you, which will help it set up its own next steps.
- Refer to any objects in files with something that is easy to search for, such as `"the MyExample class"
- `goose` *loves* to know how to run tests to get a feedback loop going, just like you do. If you tell it how you test things locally and quickly, it can make use of that when working on your project
- You can use `goose` for tasks that would require scripting at times, even looking at your screen and correcting designs/helping you fix bugs, try asking it to help you in a way you would ask a person.
- You can use `goose` for tasks that would require scripting at times, even looking at your screen and correcting designs/helping you fix bugs, try asking it to help you in a way you would ask a person.
- `goose` will make mistakes, and go in the wrong direction from times, feel free to correct it, or start again.
- You can tell `goose` to run things for you continuously (and it will iterate, try, retry) but you can also tell it to check with you before doing things (and then later on tell it to go off on its own and do its best to solve).
- `goose` can run anywhere, doesn't have to be in a repo, just ask it!


### Examples

Here are some examples that have been used:
Here are some examples that have been used:

```
G❯ Looking at the in progress changes in this repo, help me finish off the feature. CONTRIBUTING.md shows how to run the tests.
```

```
G❯ In this golang project, I want you to add open telemetry to help me get started with it. Look in the moneymovements module, run the `just test` command to check things work.
G❯ In this golang project, I want you to add open telemetry to help me get started with it. Look in the moneymovements module, run the `just test` command to check things work.
```
```
Expand All @@ -196,7 +214,7 @@ G❯ This is a fresh checkout of a golang project. I do not have my golang envir
```
```
G❯ In this repo, I want you to look at how to add a new provider for azure.
G❯ In this repo, I want you to look at how to add a new provider for azure.
Some hints are in this github issue: https://github.com/square/exchange/issues
/4 (you can use gh cli to access it).
```
Expand All @@ -213,7 +231,7 @@ G❯ I want you to help me increase the test coverage in src/java... use mvn tes
## Open Source
Yes, `goose` is open source and always will be. `goose` is released under the ASL2.0 license meaning you can use it however you like.
Yes, `goose` is open source and always will be. `goose` is released under the ASL2.0 license meaning you can use it however you like.
See LICENSE.md for more details.
To run `goose` from source, please see `CONTRIBUTING.md` for instructions on how to set up your environment and you can then run `uv run `goose` session start`.
Expand Down
4 changes: 2 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ integration *FLAGS:
uv run pytest tests -m integration {{FLAGS}}

format:
ruff check . --fix
ruff format .
uvx ruff check . --fix
uvx ruff format .

coverage *FLAGS:
uv run coverage run -m pytest tests -m "not integration" {{FLAGS}}
Expand Down
4 changes: 2 additions & 2 deletions src/goose/toolkit/developer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from rich.text import Text

from goose.toolkit.base import Toolkit, tool
from goose.toolkit.utils import get_language
from goose.toolkit.utils import get_language, render_template


def keep_unsafe_command_prompt(command: str) -> PromptType:
Expand All @@ -37,7 +37,7 @@ def system(self) -> str:
hints_path = Path(".goosehints")
system_prompt = Message.load("prompts/developer.jinja").text
if hints_path.is_file():
goosehints = hints_path.read_text()
goosehints = render_template(hints_path)
system_prompt = f"{system_prompt}\n\nHints:\n{goosehints}"
return system_prompt

Expand Down
23 changes: 23 additions & 0 deletions src/goose/toolkit/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from pathlib import Path
from typing import Optional

from pygments.lexers import get_lexer_for_filename
from pygments.util import ClassNotFound

from jinja2 import Environment, FileSystemLoader


def get_language(filename: Path) -> str:
"""
Expand All @@ -19,3 +22,23 @@ def get_language(filename: Path) -> str:
return lexer.name
except ClassNotFound:
return ""


def render_template(template_path: Path, context: Optional[dict] = None) -> str:
"""
Renders a Jinja2 template given a Pathlib path, with no context needed.
:param template_path: Path to the Jinja2 template file.
:param context: Optional dictionary containing the context for rendering the template.
:return: Rendered template as a string.
"""
# Ensure the path is absolute and exists
if not template_path.is_absolute():
template_path = template_path.resolve()

if not template_path.exists():
raise FileNotFoundError(f"Template file {template_path} does not exist.")

env = Environment(loader=FileSystemLoader(template_path.parent))
template = env.get_template(template_path.name)
return template.render(context or {})
29 changes: 27 additions & 2 deletions tests/toolkit/test_developer.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
from pathlib import Path


from tempfile import TemporaryDirectory
from unittest.mock import MagicMock, Mock

import pytest
from goose.toolkit.base import Requirements
from goose.toolkit.developer import Developer
from contextlib import contextmanager
import os


@contextmanager
def change_dir(new_dir):
"""Context manager to temporarily change the current working directory."""
original_dir = os.getcwd()
os.chdir(new_dir)
try:
yield
finally:
os.chdir(original_dir)


@pytest.fixture
Expand All @@ -30,6 +41,20 @@ def developer_toolkit():
return toolkit


def test_system_prompt_with_goosehints(temp_dir, developer_toolkit):
readme_file = temp_dir / "README.md"
readme_file.write_text("This is from the README.md file.")

hints_file = temp_dir / ".goosehints"
jinja_template_content = "Hints:\n\n{% include 'README.md' %}\nEnd."
hints_file.write_text(jinja_template_content)

with change_dir(temp_dir):
system_prompt = developer_toolkit.system()
expected_end = "Hints:\n\nThis is from the README.md file.\nEnd."
assert system_prompt.endswith(expected_end)


def test_update_plan(developer_toolkit):
tasks = [
{"description": "Task 1", "status": "planned"},
Expand Down

0 comments on commit 72d927f

Please sign in to comment.