diff --git a/.vscode/settings.json b/.vscode/settings.json index be5b3490..d0746e5f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,7 +24,10 @@ "editor.tabSize": 2, "editor.insertSpaces": true }, - + "[go]": { + "editor.tabSize": 4, + "editor.insertSpaces": true + }, "[python]": { "editor.tabSize": 4, "editor.insertSpaces": true diff --git a/agent/agent.go b/agent/agent.go new file mode 100644 index 00000000..980556f2 --- /dev/null +++ b/agent/agent.go @@ -0,0 +1,33 @@ +package agent + +import ( + "context" + "log" + + "github.com/firebase/genkit/go/ai" + "github.com/firebase/genkit/go/genkit" + "github.com/firebase/genkit/go/plugins/googlegenai" + + "github.com/memohai/Memoh/model" + "github.com/memohai/Memoh/agent/prompts" +) + +type AgentParams struct { + Model model.Model +} + +type AgentInput struct { + content string +} + +type AgentOperations struct { + Ask func(input AgentInput) (string, error) +} + +func NewAgent(params AgentParams) AgentOperations { + return AgentOperations{ + Ask: func(input AgentInput) (string, error) { + return "", nil + }, + } +} diff --git a/agent/prompts/schedule.go b/agent/prompts/schedule.go new file mode 100644 index 00000000..121588c6 --- /dev/null +++ b/agent/prompts/schedule.go @@ -0,0 +1,92 @@ +package prompts + +import ( + "bytes" + "fmt" + "strings" + "text/template" + "time" +) + +// Schedule represents a scheduled task +type Schedule struct { + ID string + Pattern string + Name string + Description string + Command string + MaxCalls *int // nil means unlimited +} + +// ScheduleParams contains parameters for generating the schedule prompt +type ScheduleParams struct { + Schedule Schedule + Locale string // e.g., "en-US", "zh-CN" + Date time.Time +} + +// scheduleTemplateData holds data for the schedule prompt template +type scheduleTemplateData struct { + Time string + Name string + Description string + ID string + MaxCalls string + Pattern string + Command string +} + +const schedulePromptTemplate = `--- +notice: **This is a scheduled task automatically send to you by the system, not the user input** +{{.Time}} +schedule-name: {{.Name}} +schedule-description: {{.Description}} +schedule-id: {{.ID}} +max-calls: {{.MaxCalls}} +cron-pattern: {{.Pattern}} +--- + +**COMMAND** + +{{.Command}}` + +var scheduleTmpl *template.Template + +func init() { + var err error + scheduleTmpl, err = template.New("schedule").Parse(schedulePromptTemplate) + if err != nil { + panic(err) + } +} + +// SchedulePrompt generates the prompt for a scheduled task +func SchedulePrompt(params ScheduleParams) string { + timeStr := Time(TimeParams{ + Date: params.Date, + Locale: params.Locale, + }) + + maxCallsStr := "Unlimited" + if params.Schedule.MaxCalls != nil { + maxCallsStr = fmt.Sprintf("%d", *params.Schedule.MaxCalls) + } + + data := scheduleTemplateData{ + Time: timeStr, + Name: params.Schedule.Name, + Description: params.Schedule.Description, + ID: params.Schedule.ID, + MaxCalls: maxCallsStr, + Pattern: params.Schedule.Pattern, + Command: params.Schedule.Command, + } + + var buf bytes.Buffer + if err := scheduleTmpl.Execute(&buf, data); err != nil { + panic(err) + } + + return strings.TrimSpace(buf.String()) +} + diff --git a/agent/prompts/shared.go b/agent/prompts/shared.go new file mode 100644 index 00000000..532fe12c --- /dev/null +++ b/agent/prompts/shared.go @@ -0,0 +1,20 @@ +package prompts + +import ( + "fmt" + "time" +) + +// TimeParams contains parameters for formatting time +type TimeParams struct { + Date time.Time + Locale string // e.g., "en-US", "zh-CN" +} + +// Time formats the date and time according to the locale +func Time(params TimeParams) string { + dateStr := params.Date.Format("2006-01-02") + timeStr := params.Date.Format("15:04:05") + + return fmt.Sprintf("date: %s\ntime: %s", dateStr, timeStr) +} diff --git a/agent/prompts/system.go b/agent/prompts/system.go new file mode 100644 index 00000000..b713b560 --- /dev/null +++ b/agent/prompts/system.go @@ -0,0 +1,112 @@ +package prompts + +import ( + "bytes" + "strings" + "text/template" + "time" +) + +// Platform represents a messaging platform +type Platform struct { + ID string + Name string + Config map[string]interface{} + Active bool +} + +// SystemParams contains parameters for generating the system prompt +type SystemParams struct { + Date time.Time + Locale string // e.g., "en-US", "zh-CN" + Language string + MaxContextLoadTime int // in minutes + Platforms []Platform + CurrentPlatform string +} + +// systemTemplateData holds data for the system prompt template +type systemTemplateData struct { + Time string + Language string + Platforms []Platform + CurrentPlatform string + MaxContextLoadTime int +} + +const systemPromptTemplate = `--- +{{.Time}} +language: {{.Language}} +available-platforms: +{{- if .Platforms}} +{{- range .Platforms}} + - {{.Name}} +{{- end}} +{{- else}} + (none) +{{- end}} +current-platform: {{.CurrentPlatform}} +--- +You are a personal housekeeper assistant, which able to manage the master's daily affairs. + +Your abilities: +- Long memory: You possess long-term memory; conversations from the last {{.MaxContextLoadTime}} minutes will be directly loaded into your context. Additionally, you can use tools to search for past memories. +- Scheduled tasks: You can create scheduled tasks to automatically remind you to do something. +- Messaging: You may allowed to use message software to send messages to the master. + +**Memory** +- Your context has been loaded from the last {{.MaxContextLoadTime}} minutes. +- You can use {{quote "search-memory"}} to search for past memories with natural language. + +**Schedule** +- We use **Cron Syntax** to schedule tasks. +- You can use {{quote "get-schedules"}} to get the list of schedules. +- You can use {{quote "remove-schedule"}} to remove a schedule by id. +- You can use {{quote "schedule"}} to schedule a task. + + The {{quote "pattern"}} is the pattern of the schedule with **Cron Syntax**. + + The {{quote "command"}} is the natural language command to execute, will send to you when the schedule is triggered, which means the command will be executed by presence of you. + + The {{quote "maxCalls"}} is the maximum number of calls to the schedule, If you want to run the task only once, set it to 1. +- The {{quote "command"}} should include the method (e.g. {{quote "send-message"}}) for returning the task result. If the user does not specify otherwise, the user should be asked how they would like to be notified. + +**Message** +- You can use {{quote "send-message"}} to send a message to the master. + + The {{quote "platform"}} is the platform to send the message to, it must be one of the {{quote "available-platforms"}}. + + The {{quote "message"}} is the message to send. + + IF: the problem is initiated by a user, regardless of the platform the user is using, the content should be directly output in the content. + + IF: the issue is initiated by a non-user (such as a scheduled task reminder), then it should be sent using the appropriate tools on the platform specified in the requirements.` + +var systemTmpl *template.Template + +func init() { + var err error + systemTmpl = template.New("system").Funcs(template.FuncMap{ + "quote": Quote, + }) + systemTmpl, err = systemTmpl.Parse(systemPromptTemplate) + if err != nil { + panic(err) + } +} + +// SystemPrompt generates the system prompt for the agent +func SystemPrompt(params SystemParams) string { + timeStr := Time(TimeParams{ + Date: params.Date, + Locale: params.Locale, + }) + + data := systemTemplateData{ + Time: timeStr, + Language: params.Language, + Platforms: params.Platforms, + CurrentPlatform: params.CurrentPlatform, + MaxContextLoadTime: params.MaxContextLoadTime, + } + + var buf bytes.Buffer + if err := systemTmpl.Execute(&buf, data); err != nil { + panic(err) + } + + return strings.TrimSpace(buf.String()) +} diff --git a/agent/prompts/utils.go b/agent/prompts/utils.go new file mode 100644 index 00000000..3fc9e0c4 --- /dev/null +++ b/agent/prompts/utils.go @@ -0,0 +1,16 @@ +package prompts + +import "fmt" + +// Quote wraps content with backticks +func Quote(content string) string { + return fmt.Sprintf("`%s`", content) +} + +// Block wraps content in a code block with optional tag +func Block(content string, tag string) string { + if tag != "" { + return fmt.Sprintf("```%s\n%s\n```", tag, content) + } + return fmt.Sprintf("```\n%s\n```", content) +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..3d0003fd --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/memohai/Memoh + +go 1.25.6 + +require github.com/firebase/genkit/go v1.4.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..ca1cc678 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/firebase/genkit/go v1.4.0 h1:CP1hNWk7z0hosyY53zMH6MFKFO1fMLtj58jGPllQo6I= +github.com/firebase/genkit/go v1.4.0/go.mod h1:HX6m7QOaGc3MDNr/DrpQZrzPLzxeuLxrkTvfFtCYlGw= diff --git a/main.go b/main.go new file mode 100644 index 00000000..85f0393b --- /dev/null +++ b/main.go @@ -0,0 +1 @@ +package main \ No newline at end of file diff --git a/model/model.go b/model/model.go new file mode 100644 index 00000000..3a518e1f --- /dev/null +++ b/model/model.go @@ -0,0 +1,10 @@ +package model + +import "github.com/memohai/Memoh/model/provider" + +type Model struct { + Provider provider.Provider + ModelID string + BaseURL string + APIKey string +} \ No newline at end of file diff --git a/model/provider/provider.go b/model/provider/provider.go new file mode 100644 index 00000000..90800c92 --- /dev/null +++ b/model/provider/provider.go @@ -0,0 +1,17 @@ +package provider + +type Provider int + +const ( + OpenAI Provider = iota + Anthropic + Google +) + +func (p Provider) String() string { + return []string{ + "openai", + "anthropic", + "google", + }[p] +} \ No newline at end of file