Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
70 changes: 70 additions & 0 deletions examples/email/README.md
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.
262 changes: 262 additions & 0 deletions examples/email/main.go
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
}
2 changes: 2 additions & 0 deletions examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,7 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.67.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
4 changes: 4 additions & 0 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,13 @@ google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ require (
golang.org/x/text v0.21.0
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.34.2
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
trpc.group/trpc-go/trpc-a2a-go v0.2.5-0.20251023030722-7f02b57fd14a
trpc.group/trpc-go/trpc-mcp-go v0.0.10
)
Expand Down Expand Up @@ -82,6 +83,7 @@ require (
golang.org/x/sys v0.30.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,13 @@ google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
Loading
Loading