-
Notifications
You must be signed in to change notification settings - Fork 55
tool: add a new email tool and an example agent for sending email #714
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
Open
201430098137
wants to merge
12
commits into
trpc-group:main
Choose a base branch
from
201430098137:feat-tool-email
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
f8fd4ad
feat(tool):implement email toolSet(send mail) and add example to use
201430098137 3ad38fe
fix:goimports and golangci-lint and remove some test
201430098137 123b3a5
fix:change email package and complete code
201430098137 e8e85e2
Merge branch 'main' into feat-tool-email
201430098137 16adf50
go.mod: go mod tidy
201430098137 1a4c778
tests: email tool add more test case
201430098137 cbd3b39
Merge branch 'main' into feat-tool-email
201430098137 270d9ee
tests: email tool add more test case
201430098137 2c07d06
doc: fix ReadMe.md
201430098137 37d2b9d
fix: adjust the instruction
201430098137 9eaa7ca
Merge branch 'main' into feat-tool-email
201430098137 87c608f
tool(email):add 163 mail support
201430098137 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| # email Example | ||
|
|
||
| This example demonstrates how to handle various types of email (qq,gmail) using OpenAI-compatible models. | ||
|
|
||
| ## Features | ||
|
|
||
| - **send email**: send email from your_email to other email, you can send whatever you want, but not support attachment | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ```bash | ||
| # set your open base url, | ||
| export OPENAI_BASE_URL="http://ieval.woa.com/openapi/v1" | ||
| # Set your API key | ||
| export OPENAI_API_KEY="your-api-key-here" | ||
|
|
||
| go run main.go -model gpt-4o-mini | ||
| ``` | ||
|
|
||
| ## Example Session | ||
|
|
||
| ``` | ||
| 🚀 Send Email Chat Demo | ||
| Model: gpt-5 | ||
| Streaming: true | ||
| Type 'exit' to end the conversation | ||
| Available tools: send_email | ||
| ================================================== | ||
| ✅ Email chat ready! Session: email-session-1763626040 | ||
| 💡 Try asking questions like: | ||
| - send an email to [email protected] | ||
| 👤 You: send an email to [email protected] | ||
| 🤖 Assistant: I can send the email for you. Please provide the following so I can proceed: | ||
| - Email account credentials for sending: account name and password | ||
| - Subject line | ||
| - Email body/content | ||
| - Optional: the display “From” name you want to appear | ||
| Recipient to confirm: [email protected] | ||
| If you prefer, I can also draft the email content first and share it with you for approval before sending. | ||
| 👤 You: name:[email protected] passwd: "xxxxxx" send an email to [email protected] subject: hello content:<html><body><h1>标题</h1><p>内容</p></body></html> | ||
| 🤖 Assistant: | ||
| 🔍 email initiated: | ||
| • email_send_email (ID: call_DxMS5B7zqSCj8jiEVx6pyG56) | ||
| Query: {"auth":{"name":"[email protected]","password":"xxxxx"},"mail_list":[{"to_email":"[email protected]","subject":"hello","content":"<html><body><h1>标题</h1><p>内容</p></body></html>"}]} | ||
| 🔄 send email... | ||
| ✅ send email results (ID: call_DxMS5B7zqSCj8jiEVx6pyG56): {"message":""} | ||
| Your email has been sent successfully. | ||
| Details: | ||
| - From: [email protected] | ||
| - To: [email protected] | ||
| - Subject: hello | ||
| - Content (HTML): | ||
| <html><body><h1>标题</h1><p>内容</p></body></html> | ||
| If you’d like to send more emails or schedule one, let me know the details. | ||
| 👤 You: exit | ||
| 👋 Goodbye! | ||
| ``` | ||
|
|
||
| ## How It Works | ||
|
|
||
| 1. **Setup**: The example creates an LLM agent with access to the email tool | ||
| 2. **User Input**: Users can ask to send email | ||
| 3. **Tool Detection**: The AI automatically decides when to use the email tool or ask more information of send email | ||
| 4. **Email Send Execution**: The email tool performs send email and returns structured results | ||
| 5. **Response Generation**: The AI uses the search results to provide informed, up-to-date responses | ||
|
|
||
| ## API Design & Limitations | ||
|
|
||
| ### Why These Limitations Exist | ||
| 1. the send email tool use smtp protocol, mailbox have speed limit of send email. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,262 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "bufio" | ||
| "context" | ||
| "flag" | ||
| "fmt" | ||
| "log" | ||
| "os" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "trpc.group/trpc-go/trpc-agent-go/agent/llmagent" | ||
| "trpc.group/trpc-go/trpc-agent-go/event" | ||
| "trpc.group/trpc-go/trpc-agent-go/model" | ||
| "trpc.group/trpc-go/trpc-agent-go/model/openai" | ||
| "trpc.group/trpc-go/trpc-agent-go/runner" | ||
| "trpc.group/trpc-go/trpc-agent-go/tool" | ||
| "trpc.group/trpc-go/trpc-agent-go/tool/email" | ||
| ) | ||
|
|
||
| var ( | ||
| streaming = flag.Bool("streaming", true, "Enable streaming mode for responses") | ||
| modelName = flag.String("model", "deepseek-chat", "Name of the model to use") | ||
| ) | ||
|
|
||
| func main() { | ||
| // Parse command line flags. | ||
| flag.Parse() | ||
|
|
||
| fmt.Printf("🚀 Send Email Chat Demo\n") | ||
| fmt.Printf("Model: %s\n", *modelName) | ||
| fmt.Printf("Streaming: %t\n", *streaming) | ||
| fmt.Printf("Type 'exit' to end the conversation\n") | ||
| fmt.Printf("Available tools: send_email\n") | ||
| fmt.Println(strings.Repeat("=", 50)) | ||
|
|
||
| // Create and run the chat. | ||
| chat := &emailChat{ | ||
| modelName: *modelName, | ||
| streaming: *streaming, | ||
| } | ||
|
|
||
| if err := chat.run(); err != nil { | ||
| log.Fatalf("Chat failed: %s", err.Error()) | ||
| } | ||
| } | ||
|
|
||
| type emailChat struct { | ||
| modelName string | ||
| runner runner.Runner | ||
| userID string | ||
| sessionID string | ||
| streaming bool | ||
| } | ||
|
|
||
| // run starts the interactive chat session. | ||
| func (c *emailChat) run() error { | ||
| ctx := context.Background() | ||
|
|
||
| // Setup the runner. | ||
| if err := c.setup(ctx); err != nil { | ||
| return fmt.Errorf("setup failed: %w", err) | ||
| } | ||
|
|
||
| // Start interactive chat. | ||
| return c.startChat(ctx) | ||
| } | ||
|
|
||
| // setup creates the runner with LLM agent and send email tool. | ||
| func (c *emailChat) setup(_ context.Context) error { | ||
| // Create OpenAI model. | ||
| modelInstance := openai.New(c.modelName) | ||
|
|
||
| // Create email tool. | ||
| // For basic usage: | ||
| emailTool, err := email.NewToolSet() | ||
| if err != nil { | ||
| return fmt.Errorf("create file tool set: %w", err) | ||
| } | ||
|
|
||
| // Create LLM agent with email tool. | ||
| genConfig := model.GenerationConfig{ | ||
| MaxTokens: intPtr(2000), | ||
| Temperature: floatPtr(0.7), | ||
| Stream: c.streaming, // Enable streaming | ||
| } | ||
|
|
||
| agentName := "email-assistant" | ||
| llmAgent := llmagent.New( | ||
| agentName, | ||
| llmagent.WithModel(modelInstance), | ||
| llmagent.WithDescription("A helpful AI assistant with access to email sending capabilities"), | ||
| llmagent.WithInstruction("Use the email tool to send emails. ask user to provide account credentials"), | ||
| llmagent.WithGenerationConfig(genConfig), | ||
| llmagent.WithToolSets([]tool.ToolSet{emailTool}), | ||
| ) | ||
|
|
||
| // Create runner. | ||
| appName := "email-agent" | ||
| c.runner = runner.NewRunner( | ||
| appName, | ||
| llmAgent, | ||
| ) | ||
|
|
||
| // Setup identifiers. | ||
| c.userID = "user" | ||
| c.sessionID = fmt.Sprintf("email-session-%d", time.Now().Unix()) | ||
|
|
||
| fmt.Printf("✅ Email chat ready! Session: %s\n\n", c.sessionID) | ||
| return nil | ||
| } | ||
|
|
||
| // startChat runs the interactive conversation loop. | ||
| func (c *emailChat) startChat(ctx context.Context) error { | ||
| scanner := bufio.NewScanner(os.Stdin) | ||
|
|
||
| // Print welcome message with examples. | ||
| fmt.Println("💡 Try asking questions like:") | ||
| fmt.Println(" - send an email to [email protected] user:your_email password:your_password subject:subject content:content") | ||
| fmt.Println() | ||
|
|
||
| for { | ||
| fmt.Print("👤 You: ") | ||
| if !scanner.Scan() { | ||
| break | ||
| } | ||
|
|
||
| userInput := strings.TrimSpace(scanner.Text()) | ||
| if userInput == "" { | ||
| continue | ||
| } | ||
|
|
||
| // Handle exit command. | ||
| if strings.ToLower(userInput) == "exit" { | ||
| fmt.Println("👋 Goodbye!") | ||
| return nil | ||
| } | ||
|
|
||
| // Process the user message. | ||
| if err := c.processMessage(ctx, userInput); err != nil { | ||
| fmt.Printf("❌ Error: %v\n", err) | ||
| } | ||
|
|
||
| fmt.Println() // Add spacing between turns | ||
| } | ||
|
|
||
| if err := scanner.Err(); err != nil { | ||
| return fmt.Errorf("input scanner error: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // processMessage handles a single message exchange. | ||
| func (c *emailChat) processMessage(ctx context.Context, userMessage string) error { | ||
| message := model.NewUserMessage(userMessage) | ||
| // Run the agent through the runner. | ||
| eventChan, err := c.runner.Run(ctx, c.userID, c.sessionID, message) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to run agent: %w", err) | ||
| } | ||
| // Process streaming response. | ||
| return c.processResponse(eventChan) | ||
| } | ||
|
|
||
| // processResponse handles the response with email tool visualization. | ||
| func (c *emailChat) processResponse(eventChan <-chan *event.Event) error { | ||
| fmt.Print("🤖 Assistant: ") | ||
|
|
||
| var ( | ||
| fullContent string | ||
| toolCallsDetected bool | ||
| assistantStarted bool | ||
| ) | ||
|
|
||
| for event := range eventChan { | ||
|
|
||
| // Handle errors. | ||
| if event.Error != nil { | ||
| fmt.Printf("\n❌ Error: %s\n", event.Error.Message) | ||
| continue | ||
| } | ||
|
|
||
| // Detect and display tool calls. | ||
| if len(event.Response.Choices) > 0 && len(event.Response.Choices[0].Message.ToolCalls) > 0 { | ||
| toolCallsDetected = true | ||
| if assistantStarted { | ||
| fmt.Printf("\n") | ||
| } | ||
| fmt.Printf("🔍 email initiated:\n") | ||
| for _, toolCall := range event.Response.Choices[0].Message.ToolCalls { | ||
| fmt.Printf(" • %s (ID: %s)\n", toolCall.Function.Name, toolCall.ID) | ||
| if len(toolCall.Function.Arguments) > 0 { | ||
| fmt.Printf(" Query: %s\n", string(toolCall.Function.Arguments)) | ||
| } | ||
| } | ||
| fmt.Printf("\n🔄 send email...\n") | ||
| } | ||
|
|
||
| // Detect tool responses. | ||
| if event.Response != nil && len(event.Response.Choices) > 0 { | ||
| hasToolResponse := false | ||
| for _, choice := range event.Response.Choices { | ||
| if choice.Message.Role == model.RoleTool && choice.Message.ToolID != "" { | ||
| fmt.Printf("✅ send email results (ID: %s): %s\n", | ||
| choice.Message.ToolID, | ||
| strings.TrimSpace(choice.Message.Content)) | ||
| hasToolResponse = true | ||
| } | ||
| } | ||
| if hasToolResponse { | ||
| continue | ||
| } | ||
| } | ||
|
|
||
| // Process content from choices. | ||
| if len(event.Response.Choices) > 0 { | ||
| choice := event.Response.Choices[0] | ||
|
|
||
| if !assistantStarted { | ||
| if toolCallsDetected { | ||
| fmt.Printf("\n🤖 Assistant: ") | ||
| } | ||
| assistantStarted = true | ||
| } | ||
|
|
||
| // Handle content based on streaming mode. | ||
| var content string | ||
| if c.streaming { | ||
| // Streaming mode: use delta content. | ||
| content = choice.Delta.Content | ||
| } else { | ||
| // Non-streaming mode: use full message content. | ||
| content = choice.Message.Content | ||
| } | ||
|
|
||
| if content != "" { | ||
| fmt.Print(content) | ||
| fullContent += content | ||
| } | ||
| } | ||
|
|
||
| // Check if this is the final event. | ||
| if event.Done { | ||
| fmt.Printf("\n") | ||
| break | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // intPtr returns a pointer to the given int. | ||
| func intPtr(i int) *int { | ||
| return &i | ||
| } | ||
|
|
||
| // floatPtr returns a pointer to the given float64. | ||
| func floatPtr(f float64) *float64 { | ||
| return &f | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.