Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: clear separation of public and internal packages #39

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
83ecb6c
initial change
lifeizhou-ap Sep 3, 2024
744c7f9
moved configuration constants to config module
lifeizhou-ap Sep 3, 2024
67935c5
moved session_path from config to session_file util
lifeizhou-ap Sep 4, 2024
9bf35a6
created _internal package to have functions and classes that used int…
lifeizhou-ap Sep 4, 2024
f2a9564
moved toolkit to _internal package, and leave the Toolkit class file …
lifeizhou-ap Sep 4, 2024
081cb9b
moved toolkit test
lifeizhou-ap Sep 4, 2024
66d0d5e
removed unused file utils
lifeizhou-ap Sep 4, 2024
7232562
created profile package in internal folder
lifeizhou-ap Sep 4, 2024
76f8d1c
move get_language to public as it is used by plugins
lifeizhou-ap Sep 4, 2024
0b0ebd7
Merge branch 'main' into lifei/refactor-distinguish-private-public-fu…
lifeizhou-ap Sep 4, 2024
252c36a
reorganised files in the toolkit folder
lifeizhou-ap Sep 4, 2024
513249f
created github folder for github toolkit
lifeizhou-ap Sep 4, 2024
0e0fa7f
moved cli prompt into internal folder
lifeizhou-ap Sep 4, 2024
061fc93
move main.py and session.py to internal folder
lifeizhou-ap Sep 4, 2024
448a7e6
removed unused load_provider function
lifeizhou-ap Sep 4, 2024
a8d766e
mark private function in session object
lifeizhou-ap Sep 4, 2024
11e6a34
move session and main tests
lifeizhou-ap Sep 4, 2024
2e1d9e5
Merge branch 'main' into lifei/refactor-distinguish-private-public-fu…
lifeizhou-ap Sep 4, 2024
87854db
fixed the format
lifeizhou-ap Sep 4, 2024
f7594c9
fixed the test
lifeizhou-ap Sep 4, 2024
48fcd1d
used proper print
lifeizhou-ap Sep 4, 2024
6658937
created pluginbase package. This can be accessed by external plugins…
lifeizhou-ap Sep 4, 2024
dbd39f7
reorganised the uitls to exposed minimal utils to be accessed by plu…
lifeizhou-ap Sep 4, 2024
ef72ff0
fixed format
lifeizhou-ap Sep 4, 2024
cae81be
organise command
lifeizhou-ap Sep 4, 2024
d3dbfbc
fixed format issue
lifeizhou-ap Sep 4, 2024
5e943de
refactored utils files
lifeizhou-ap Sep 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ in the goose package thanks to [plugin metadata][plugin]!), create a class that
import os
import platform

from goose.toolkit.base import Toolkit, tool
from goose.toolkit import Toolkit, tool


class Demo(Toolkit):
Expand Down
16 changes: 8 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,24 @@ packages = ["src/goose"]
goose-ai = "goose.module_name"

[project.entry-points."goose.toolkit"]
developer = "goose.toolkit.developer:Developer"
github = "goose.toolkit.github:Github"
screen = "goose.toolkit.screen:Screen"
repo_context = "goose.toolkit.repo_context.repo_context:RepoContext"
developer = "goose._internal.toolkit:Developer"
github = "goose._internal.toolkit:Github"
screen = "goose._internal.toolkit:Screen"
repo_context = "goose._internal.toolkit:RepoContext"

[project.entry-points."goose.profile"]
default = "goose.profile:default_profile"
default = "goose._internal.profile:default_profile"

[project.entry-points."goose.command"]
file = "goose.command.file:FileCommand"
file = "goose._internal.cli.command.file:FileCommand"

[project.entry-points."goose.cli.group"]
goose = "goose.cli.main:goose_cli"
goose = "goose._internal.cli.main:goose_cli"

[project.entry-points."goose.cli.group_option"]

[project.scripts]
goose = "goose.cli.main:cli"
goose = "goose._internal.cli.main:cli"

[build-system]
requires = ["hatchling"]
Expand Down
5 changes: 5 additions & 0 deletions src/goose/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .pluginbase.notifier import Notifier # noqa: F401
from .pluginbase.toolkit import Toolkit, tool # noqa: F401
from .pluginbase.profile import Profile, ToolkitSpec # noqa: F401
from .pluginbase.command import Command # noqa: F401
from .pluginbase.utils import converter # noqa: F401
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from functools import cache
from typing import Dict

from goose.command.base import Command
from goose.utils import load_plugins
from goose.pluginbase.command import Command
from ...utils import load_plugins


@cache
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from prompt_toolkit.completion import Completion

from goose.command.base import Command
from goose.pluginbase.command import Command


class FileCommand(Command):
Expand Down
8 changes: 4 additions & 4 deletions src/goose/cli/main.py → src/goose/_internal/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
from rich import print
from ruamel.yaml import YAML

from goose.cli.config import SESSIONS_PATH
from goose.cli.session import Session
from goose.utils import load_plugins
from goose.utils.session_file import list_sorted_session_files
from ...config import SESSIONS_PATH
from .session.session import Session
from ..utils import load_plugins
from goose.pluginbase.utils.session_file import list_sorted_session_files


@click.group()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
from prompt_toolkit.document import Document

from goose.command.base import Command
from goose.pluginbase.command import Command


class GoosePromptCompleter(Completer):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from prompt_toolkit.keys import Keys
from prompt_toolkit.styles import Style

from goose.cli.prompt.completer import GoosePromptCompleter
from goose.cli.prompt.lexer import PromptLexer
from goose.command import get_commands
from .completer import GoosePromptCompleter
from .lexer import PromptLexer
from ..command import get_commands


def create_prompt() -> PromptSession:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.validation import DummyValidator

from goose.cli.prompt.create import create_prompt
from goose.cli.prompt.prompt_validator import PromptValidator
from goose.cli.prompt.user_input import PromptAction, UserInput
from .create import create_prompt
from .prompt_validator import PromptValidator
from .user_input import PromptAction, UserInput


class GoosePromptSession:
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,70 +5,20 @@
from exchange import Message, ToolResult, ToolUse, Text
from prompt_toolkit.shortcuts import confirm
from rich import print
from rich.console import RenderableType
from rich.live import Live
from rich.markdown import Markdown
from rich.panel import Panel
from rich.status import Status

from goose.build import build_exchange
from goose.cli.config import (
default_profiles,
ensure_config,
read_config,
session_path,
)
from goose.cli.prompt.goose_prompt_session import GoosePromptSession
from goose.notifier import Notifier
from goose.profile import Profile
from goose.utils import droid, load_plugins
from goose.utils.session_file import read_from_file, write_to_file
from .session_utils import random_session_name

RESUME_MESSAGE = "I see we were interrupted. How can I help you?"


def load_provider() -> str:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleted this this function as it is not used

# We try to infer a provider, by going in order of what will auth
providers = load_plugins(group="exchange.provider")
for provider, cls in providers.items():
try:
cls.from_env()
print(Panel(f"[green]Detected an available provider: [/]{provider}"))
return provider
except Exception:
pass
else:
# TODO link to auth docs
print(
Panel(
"[red]Could not authenticate any providers[/]\n"
+ "Returning a default pointing to openai, but you will need to set an API token env variable."
)
)
return "openai"


def load_profile(name: Optional[str]) -> Profile:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved this function in the _internal.profile package

if name is None:
name = "default"
from .session_notifier import SessionNotifier

# If the name is one of the default values, we ensure a valid configuration
if name in default_profiles():
return ensure_config(name)
from ...profile.config import load_profile
from ...exchange.build import build_exchange
from ..prompt.goose_prompt_session import GoosePromptSession
from goose.pluginbase.utils.session_file import read_from_file, write_to_file, session_path

# Otherwise this is a custom config and we return it from the config file
return read_config()[name]


class SessionNotifier(Notifier):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved this class in a separate file

def __init__(self, status_indicator: Status) -> None:
self.status_indicator = status_indicator

def log(self, content: RenderableType) -> None:
print(content)

def status(self, status: str) -> None:
self.status_indicator.update(status)
RESUME_MESSAGE = "I see we were interrupted. How can I help you?"


class Session:
Expand All @@ -92,7 +42,7 @@ def __init__(
self.exchange = build_exchange(profile=load_profile(profile), notifier=notifier)

if name is not None and self.session_file_path.exists():
messages = self.load_session()
messages = self._load_session()

if messages and messages[-1].role == "user":
if type(messages[-1].content[-1]) is Text:
Expand All @@ -112,11 +62,11 @@ def __init__(
self.exchange.messages.extend(messages)

if len(self.exchange.messages) == 0 and plan:
self.setup_plan(plan=plan)
self._setup_plan(plan=plan)

self.prompt_session = GoosePromptSession.create_prompt_session()

def setup_plan(self, plan: dict) -> None:
def _setup_plan(self, plan: dict) -> None:
if len(self.exchange.messages):
raise ValueError("The plan can only be set on an empty session.")
self.exchange.messages.append(Message.user(plan["kickoff_message"]))
Expand All @@ -127,7 +77,7 @@ def setup_plan(self, plan: dict) -> None:
plan_tool_use = ToolUse(id="initialplan", name="update_plan", parameters=dict(tasks=tasks))
self.exchange.add_tool_use(plan_tool_use)

def process_first_message(self) -> Optional[Message]:
def _process_first_message(self) -> Optional[Message]:
# Get a first input unless it has been specified, such as by a plan
if len(self.exchange.messages) == 0 or self.exchange.messages[-1].role == "assistant":
user_input = self.prompt_session.get_user_input()
Expand All @@ -141,14 +91,14 @@ def run(self) -> None:
Runs the main loop to handle user inputs and responses.
Continues until an empty string is returned from the prompt.
"""
message = self.process_first_message()
message = self._process_first_message()
while message: # Loop until no input (empty string).
with Live(self.status_indicator, refresh_per_second=8, transient=True):
try:
self.exchange.add(message)
self.reply() # Process the user message.
self._reply() # Process the user message.
except KeyboardInterrupt:
self.interrupt_reply()
self._interrupt_reply()
except Exception:
print(traceback.format_exc())
if self.exchange.messages:
Expand All @@ -164,9 +114,9 @@ def run(self) -> None:
user_input = self.prompt_session.get_user_input()
message = Message.user(text=user_input.text) if user_input.to_continue() else None

self.save_session()
self._save_session()

def reply(self) -> None:
def _reply(self) -> None:
"""Reply to the last user message, calling tools as needed

Args:
Expand All @@ -190,7 +140,7 @@ def reply(self) -> None:
if response.text:
print(Markdown(response.text))

def interrupt_reply(self) -> None:
def _interrupt_reply(self) -> None:
"""Recover from an interruption at an arbitrary state"""
# Default recovery message if no user message is pending.
recovery = "We interrupted before the next processing started."
Expand Down Expand Up @@ -224,28 +174,28 @@ def interrupt_reply(self) -> None:
def session_file_path(self) -> Path:
return session_path(self.name)

def save_session(self) -> None:
def _save_session(self) -> None:
"""Save the current session to a file in JSON format."""
if self.name is None:
self.generate_session_name()
self._generate_session_name()

try:
if self.session_file_path.exists():
if not confirm(f"Session {self.name} exists in {self.session_file_path}, overwrite?"):
self.generate_session_name()
self._generate_session_name()
write_to_file(self.session_file_path, self.exchange.messages)
except PermissionError as e:
raise RuntimeError(f"Failed to save session due to permissions: {e}")
except (IOError, OSError) as e:
raise RuntimeError(f"Failed to save session due to I/O error: {e}")

def load_session(self) -> List[Message]:
def _load_session(self) -> List[Message]:
"""Load a session from a JSON file."""
return read_from_file(self.session_file_path)

def generate_session_name(self) -> None:
def _generate_session_name(self) -> None:
user_entered_session_name = self.prompt_session.get_save_session_name()
self.name = user_entered_session_name if user_entered_session_name else droid()
self.name = user_entered_session_name if user_entered_session_name else random_session_name()
print(f"Saving to [bold cyan]{self.session_file_path}[/bold cyan]")


Expand Down
15 changes: 15 additions & 0 deletions src/goose/_internal/cli/session/session_notifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from goose.pluginbase.notifier import Notifier
from rich.status import Status
from rich.console import RenderableType
from rich import print


class SessionNotifier(Notifier):
def __init__(self, status_indicator: Status) -> None:
self.status_indicator = status_indicator

def log(self, content: RenderableType) -> None:
print(content)

def status(self, status: str) -> None:
self.status_indicator.update(status)
12 changes: 12 additions & 0 deletions src/goose/_internal/cli/session/session_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import random
import string

def random_session_name() -> str:
return "".join(
[
random.choice(string.ascii_lowercase),
random.choice(string.digits),
random.choice(string.ascii_lowercase),
random.choice(string.digits),
]
)
10 changes: 5 additions & 5 deletions src/goose/build.py → src/goose/_internal/exchange/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
from exchange.moderators import get_moderator
from exchange.providers import get_provider

from goose.notifier import Notifier
from goose.profile import Profile
from goose.toolkit import get_toolkit
from goose.toolkit.base import Requirements
from goose.view import ExchangeView
from goose.pluginbase.notifier import Notifier
from goose.pluginbase.profile import Profile
from goose.pluginbase.toolkit import Requirements
from ..toolkit import get_toolkit
from .view import ExchangeView


def build_exchange(profile: Profile, notifier: Notifier) -> Exchange:
Expand Down
File renamed without changes.
File renamed without changes.
16 changes: 16 additions & 0 deletions src/goose/_internal/profile/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import Any, Dict
from goose.pluginbase.profile import Profile, ToolkitSpec


def default_profile(provider: str, processor: str, accelerator: str, **kwargs: Dict[str, Any]) -> Profile:
"""Get the default profile"""

# TODO consider if the providers should have recommended models

return Profile(
provider=provider,
processor=processor,
accelerator=accelerator,
moderator="truncate",
toolkits=[ToolkitSpec("developer")],
)
Loading
Loading