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

feat: add agent and thread workspace file CRUD #3

Merged
merged 1 commit into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ vendor/
# Ignore OS specific files
.DS_Store
Thumbs.db

# Ignore local DB files
otto.db
otto.db-*

35 changes: 23 additions & 12 deletions pkg/agents/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,45 @@ import (
"github.com/gptscript-ai/go-gptscript"
"github.com/gptscript-ai/otto/pkg/storage"
"github.com/gptscript-ai/otto/pkg/storage/apis/otto.gptscript.ai/v1"
"github.com/gptscript-ai/otto/pkg/workspace"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
)

var DefaultAgentParams = []string{
"message", "Message to send to the agent",
}

func Render(ctx context.Context, db storage.Client, namespace string, manifest v1.Manifest) ([]gptscript.ToolDef, error) {
func Render(ctx context.Context, db storage.Client, agent *v1.Agent, thread *v1.Thread, knowledgeTool, knowledgeBin string) ([]gptscript.ToolDef, []string, error) {
var extraEnv []string
t := []gptscript.ToolDef{{
Name: manifest.Name,
Description: manifest.Description,
Name: agent.Spec.Manifest.Name,
Description: agent.Spec.Manifest.Description,
Chat: true,
Tools: manifest.Tools,
Arguments: manifest.GetParams(),
Instructions: manifest.Prompt,
Tools: agent.Spec.Manifest.Tools,
Arguments: agent.Spec.Manifest.GetParams(),
Instructions: agent.Spec.Manifest.Prompt,
Type: "agent",
}}

if len(manifest.Agents) == 0 {
return t, nil
if agent.Status.HasKnowledge || thread.Status.HasKnowledge {
t[0].Tools = append(t[0].Tools, knowledgeTool)
extraEnv = append(extraEnv,
fmt.Sprintf("KNOWLEDGE_BIN=%s", knowledgeBin),
fmt.Sprintf("GPTSCRIPT_SCRIPT_ID=%s", workspace.KnowledgeIDFromWorkspaceID(agent.Spec.KnowledgeWorkspaceID)),
fmt.Sprintf("GPTSCRIPT_THREAD_ID=%s", workspace.KnowledgeIDFromWorkspaceID(thread.Spec.KnowledgeWorkspaceID)),
)
}

agents, err := ByName(ctx, db, namespace)
if len(agent.Spec.Manifest.Agents) == 0 {
return t, extraEnv, nil
}

agents, err := ByName(ctx, db, agent.Namespace)
if err != nil {
return nil, err
return nil, nil, err
}

for _, agentRef := range manifest.Agents {
for _, agentRef := range agent.Spec.Manifest.Agents {
agent, ok := agents[agentRef]
if !ok {
continue
Expand All @@ -61,7 +72,7 @@ func Render(ctx context.Context, db storage.Client, namespace string, manifest v
t = append(t, toolDef)
}

return t, nil
return t, extraEnv, nil
}

func ByName(ctx context.Context, db storage.Client, namespace string) (map[string]v1.Agent, error) {
Expand Down
114 changes: 113 additions & 1 deletion pkg/api/handlers/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package handlers
import (
"context"
"errors"
"fmt"
"net/http"

"github.com/BurntSushi/toml"
Expand Down Expand Up @@ -142,7 +143,7 @@ func (a *AgentHandler) Create(ctx context.Context, req api.Request) error {
return err
}

req.ResponseWriter.WriteHeader(http.StatusCreated)
req.WriteHeader(http.StatusCreated)
return req.JSON(convertAgent(agent, api.GetURLPrefix(req)))
}

Expand Down Expand Up @@ -172,3 +173,114 @@ func (a *AgentHandler) List(_ context.Context, req api.Request) error {

return req.JSON(resp)
}

func (a *AgentHandler) Files(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
agent v2.Agent
)
if err := req.Get(&agent, id); err != nil {
return fmt.Errorf("failed to get agent with id %s: %w", id, err)
}

return listFiles(ctx, req, a.WorkspaceClient, agent.Spec.WorkspaceID)
}

func (a *AgentHandler) UploadFile(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
agent v2.Agent
)
if err := req.Get(&agent, id); err != nil {
return fmt.Errorf("failed to get agent with id %s: %w", id, err)
}

return uploadFile(ctx, req, a.WorkspaceClient, agent.Spec.WorkspaceID)
}

func (a *AgentHandler) DeleteFile(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
filename = req.Request.PathValue("file")
agent v2.Agent
)

if err := req.Get(&agent, id); err != nil {
return fmt.Errorf("failed to get agent with id %s: %w", id, err)
}

return deleteFile(ctx, req, a.WorkspaceClient, agent.Spec.WorkspaceID, filename)
}

func (a *AgentHandler) Knowledge(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
agent v2.Agent
)
if err := req.Get(&agent, id); err != nil {
return fmt.Errorf("failed to get agent with id %s: %w", id, err)
}

return listFiles(ctx, req, a.WorkspaceClient, agent.Spec.KnowledgeWorkspaceID)
}

func (a *AgentHandler) UploadKnowledge(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
agent v2.Agent
)
if err := req.Get(&agent, id); err != nil {
return fmt.Errorf("failed to get agent with id %s: %w", id, err)
}

if err := uploadFile(ctx, req, a.WorkspaceClient, agent.Spec.KnowledgeWorkspaceID); err != nil {
return err
}

agent.Status.IngestKnowledge = true
agent.Status.HasKnowledge = true
return req.Storage.Status().Update(ctx, &agent)
}

func (a *AgentHandler) DeleteKnowledge(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
filename = req.Request.PathValue("file")
agent v2.Agent
)

if err := req.Get(&agent, id); err != nil {
return fmt.Errorf("failed to get agent with id %s: %w", id, err)
}

if err := deleteFile(ctx, req, a.WorkspaceClient, agent.Spec.KnowledgeWorkspaceID, filename); err != nil {
return err
}

agent.Status.IngestKnowledge = true
return req.Storage.Status().Update(ctx, &agent)
}

func (a *AgentHandler) IngestKnowledge(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
agent v2.Agent
)
if err := req.Get(&agent, id); err != nil {
return fmt.Errorf("failed to get agent with id %s: %w", id, err)
}

files, err := a.WorkspaceClient.Ls(ctx, agent.Spec.KnowledgeWorkspaceID)
if err != nil {
return err
}

req.WriteHeader(http.StatusNoContent)

if len(files) == 0 && !agent.Status.HasKnowledge {
return nil
}

agent.Status.IngestKnowledge = true
return req.Storage.Status().Update(ctx, &agent)
}
52 changes: 52 additions & 0 deletions pkg/api/handlers/files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package handlers

import (
"context"
"fmt"
"io"
"net/http"

"github.com/gptscript-ai/otto/pkg/api"
"github.com/gptscript-ai/otto/pkg/api/types"
wclient "github.com/thedadams/workspace-provider/pkg/client"
)

func listFiles(ctx context.Context, req api.Request, wc *wclient.Client, workspaceID string) error {
files, err := wc.Ls(ctx, workspaceID)
if err != nil {
return fmt.Errorf("failed to list files in workspace %q: %w", workspaceID, err)
}

return req.JSON(types.FileList{Items: files})
}

func uploadFile(ctx context.Context, req api.Request, wc *wclient.Client, workspaceID string) error {
file := req.Request.PathValue("file")
if file == "" {
return fmt.Errorf("file path parameter is required")
}

writer, err := wc.WriteFile(ctx, workspaceID, file)
if err != nil {
return fmt.Errorf("failed to upload file %q to workspace %q: %w", file, workspaceID, err)
}

_, err = io.Copy(writer, req.Request.Body)
if err != nil {
return fmt.Errorf("failed to write file %q to workspace %q: %w", file, workspaceID, err)
}

req.WriteHeader(http.StatusCreated)

return nil
}

func deleteFile(ctx context.Context, req api.Request, wc *wclient.Client, workspaceID, filename string) error {
if err := wc.DeleteFile(ctx, workspaceID, filename); err != nil {
return fmt.Errorf("failed to delete file %q from workspace %q: %w", filename, workspaceID, err)
}

req.WriteHeader(http.StatusNoContent)

return nil
}
114 changes: 114 additions & 0 deletions pkg/api/handlers/threads.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package handlers

import (
"context"
"fmt"
"net/http"

"github.com/gptscript-ai/otto/pkg/api"
"github.com/gptscript-ai/otto/pkg/api/types"
"github.com/gptscript-ai/otto/pkg/storage/apis/otto.gptscript.ai/v1"
wclient "github.com/thedadams/workspace-provider/pkg/client"
)

type ThreadHandler struct {
WorkspaceClient *wclient.Client
}

func convertThread(thread v1.Thread) types.Thread {
Expand Down Expand Up @@ -43,3 +47,113 @@ func (a *ThreadHandler) List(_ context.Context, req api.Request) error {

return req.JSON(resp)
}
func (a *ThreadHandler) Files(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
thread v1.Thread
)
if err := req.Get(&thread, id); err != nil {
return fmt.Errorf("failed to get thread with id %s: %w", id, err)
}

return listFiles(ctx, req, a.WorkspaceClient, thread.Spec.WorkspaceID)
}

func (a *ThreadHandler) UploadFile(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
thread v1.Thread
)
if err := req.Get(&thread, id); err != nil {
return fmt.Errorf("failed to get thread with id %s: %w", id, err)
}

return uploadFile(ctx, req, a.WorkspaceClient, thread.Spec.WorkspaceID)
}

func (a *ThreadHandler) DeleteFile(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
filename = req.Request.PathValue("file")
thread v1.Thread
)

if err := req.Get(&thread, id); err != nil {
return fmt.Errorf("failed to get thread with id %s: %w", id, err)
}

return deleteFile(ctx, req, a.WorkspaceClient, thread.Spec.WorkspaceID, filename)
}

func (a *ThreadHandler) Knowledge(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
thread v1.Thread
)
if err := req.Get(&thread, id); err != nil {
return fmt.Errorf("failed to get thread with id %s: %w", id, err)
}

return listFiles(ctx, req, a.WorkspaceClient, thread.Spec.KnowledgeWorkspaceID)
}

func (a *ThreadHandler) UploadKnowledge(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
thread v1.Thread
)
if err := req.Get(&thread, id); err != nil {
return fmt.Errorf("failed to get thread with id %s: %w", id, err)
}

if err := uploadFile(ctx, req, a.WorkspaceClient, thread.Spec.KnowledgeWorkspaceID); err != nil {
return err
}

thread.Status.IngestKnowledge = true
thread.Status.HasKnowledge = true
return req.Storage.Status().Update(ctx, &thread)
}

func (a *ThreadHandler) DeleteKnowledge(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
filename = req.Request.PathValue("file")
thread v1.Thread
)

if err := req.Get(&thread, id); err != nil {
return fmt.Errorf("failed to get thread with id %s: %w", id, err)
}

if err := deleteFile(ctx, req, a.WorkspaceClient, thread.Spec.KnowledgeWorkspaceID, filename); err != nil {
return err
}

thread.Status.IngestKnowledge = true
return req.Storage.Status().Update(ctx, &thread)
}

func (a *ThreadHandler) IngestKnowledge(ctx context.Context, req api.Request) error {
var (
id = req.Request.PathValue("id")
thread v1.Thread
)
if err := req.Get(&thread, id); err != nil {
return fmt.Errorf("failed to get thread with id %s: %w", id, err)
}

files, err := a.WorkspaceClient.Ls(ctx, thread.Spec.KnowledgeWorkspaceID)
if err != nil {
return err
}

req.WriteHeader(http.StatusNoContent)

if len(files) == 0 && !thread.Status.HasKnowledge {
return nil
}

thread.Status.IngestKnowledge = true
return req.Storage.Status().Update(ctx, &thread)
}
Loading