ai-agents 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +29 -106
- data/docs/Gemfile +14 -0
- data/docs/Gemfile.lock +183 -0
- data/docs/_config.yml +53 -0
- data/docs/_sass/color_schemes/ruby.scss +72 -0
- data/docs/_sass/custom/custom.scss +93 -0
- data/docs/architecture.md +353 -0
- data/docs/assets/fonts/InterVariable.woff2 +0 -0
- data/docs/concepts/agent-tool.md +166 -0
- data/docs/concepts/agents.md +43 -0
- data/docs/concepts/context.md +110 -0
- data/docs/concepts/handoffs.md +81 -0
- data/docs/concepts/runner.md +87 -0
- data/docs/concepts/tools.md +62 -0
- data/docs/concepts.md +21 -0
- data/docs/guides/agent-as-tool-pattern.md +242 -0
- data/docs/guides/multi-agent-systems.md +261 -0
- data/docs/guides/rails-integration.md +440 -0
- data/docs/guides/state-persistence.md +451 -0
- data/docs/guides.md +18 -0
- data/docs/index.md +95 -0
- data/examples/collaborative-copilot/README.md +169 -0
- data/examples/collaborative-copilot/agents/analysis_agent.rb +48 -0
- data/examples/collaborative-copilot/agents/answer_suggestion_agent.rb +50 -0
- data/examples/collaborative-copilot/agents/copilot_orchestrator.rb +85 -0
- data/examples/collaborative-copilot/agents/integrations_agent.rb +58 -0
- data/examples/collaborative-copilot/agents/research_agent.rb +52 -0
- data/examples/collaborative-copilot/data/contacts.json +47 -0
- data/examples/collaborative-copilot/data/conversations.json +170 -0
- data/examples/collaborative-copilot/data/knowledge_base.json +58 -0
- data/examples/collaborative-copilot/data/linear_issues.json +83 -0
- data/examples/collaborative-copilot/data/stripe_billing.json +71 -0
- data/examples/collaborative-copilot/interactive.rb +90 -0
- data/examples/collaborative-copilot/tools/create_linear_ticket_tool.rb +58 -0
- data/examples/collaborative-copilot/tools/get_article_tool.rb +41 -0
- data/examples/collaborative-copilot/tools/get_contact_tool.rb +51 -0
- data/examples/collaborative-copilot/tools/get_conversation_tool.rb +53 -0
- data/examples/collaborative-copilot/tools/get_stripe_billing_tool.rb +44 -0
- data/examples/collaborative-copilot/tools/search_contacts_tool.rb +57 -0
- data/examples/collaborative-copilot/tools/search_conversations_tool.rb +54 -0
- data/examples/collaborative-copilot/tools/search_knowledge_base_tool.rb +55 -0
- data/examples/collaborative-copilot/tools/search_linear_issues_tool.rb +60 -0
- data/examples/isp-support/agents_factory.rb +57 -1
- data/examples/isp-support/tools/create_lead_tool.rb +16 -2
- data/examples/isp-support/tools/crm_lookup_tool.rb +13 -1
- data/lib/agents/agent.rb +52 -6
- data/lib/agents/agent_tool.rb +113 -0
- data/lib/agents/handoff.rb +8 -34
- data/lib/agents/tool_context.rb +36 -0
- data/lib/agents/version.rb +1 -1
- data/lib/agents.rb +1 -0
- metadata +44 -2
@@ -0,0 +1,58 @@
|
|
1
|
+
{
|
2
|
+
"articles": {
|
3
|
+
"ART-001": {
|
4
|
+
"id": "ART-001",
|
5
|
+
"title": "How to Clear Browser Cache",
|
6
|
+
"category": "troubleshooting",
|
7
|
+
"content": "To clear your browser cache:\n\n**Chrome:**\n1. Press Ctrl+Shift+Delete (Cmd+Shift+Delete on Mac)\n2. Select 'All time' from the time range\n3. Check 'Cached images and files'\n4. Click 'Clear data'\n\n**Firefox:**\n1. Press Ctrl+Shift+Delete (Cmd+Shift+Delete on Mac)\n2. Select 'Everything' from time range\n3. Check 'Cache'\n4. Click 'Clear Now'\n\n**Safari:**\n1. Go to Safari menu > Clear History\n2. Select 'All History'\n3. Click 'Clear History'",
|
8
|
+
"tags": ["browser", "cache", "troubleshooting", "login-issues"],
|
9
|
+
"created_at": "2023-08-15T10:00:00Z",
|
10
|
+
"updated_at": "2024-01-10T14:30:00Z"
|
11
|
+
},
|
12
|
+
"ART-002": {
|
13
|
+
"id": "ART-002",
|
14
|
+
"title": "Password Reset Instructions",
|
15
|
+
"category": "account",
|
16
|
+
"content": "If you're unable to log in, try resetting your password:\n\n1. Go to the login page\n2. Click 'Forgot Password?'\n3. Enter your email address\n4. Check your email for reset instructions\n5. Click the reset link (valid for 24 hours)\n6. Create a new strong password\n\n**Password Requirements:**\n- At least 8 characters\n- One uppercase letter\n- One lowercase letter \n- One number\n- One special character\n\nIf you don't receive the reset email, check your spam folder or contact support.",
|
17
|
+
"tags": ["password", "reset", "login", "account"],
|
18
|
+
"created_at": "2023-08-15T10:15:00Z",
|
19
|
+
"updated_at": "2024-01-05T09:20:00Z"
|
20
|
+
},
|
21
|
+
"ART-003": {
|
22
|
+
"id": "ART-003",
|
23
|
+
"title": "API Rate Limits and Error Handling",
|
24
|
+
"category": "development",
|
25
|
+
"content": "Our API has the following rate limits:\n\n**Standard Plan:** 1,000 requests/hour\n**Pro Plan:** 5,000 requests/hour \n**Enterprise Plan:** 50,000 requests/hour\n\n**Common Error Codes:**\n- 429: Rate limit exceeded\n- 500: Internal server error\n- 503: Service temporarily unavailable\n\n**Best Practices:**\n- Implement exponential backoff for 5xx errors\n- Cache responses when possible\n- Use pagination for large datasets\n- Monitor your usage in the dashboard\n\nIf you're experiencing consistent 500 errors, please contact our engineering team.",
|
26
|
+
"tags": ["api", "rate-limits", "errors", "development"],
|
27
|
+
"created_at": "2023-09-01T11:00:00Z",
|
28
|
+
"updated_at": "2024-01-12T16:45:00Z"
|
29
|
+
},
|
30
|
+
"ART-004": {
|
31
|
+
"id": "ART-004",
|
32
|
+
"title": "Billing and Refund Policy",
|
33
|
+
"category": "billing",
|
34
|
+
"content": "**Billing Cycle:**\nAll plans are billed monthly on the date you signed up.\n\n**Refund Policy:**\n- Full refund within 30 days of signup\n- Partial refunds for downgrades (prorated)\n- No refunds for accounts terminated for policy violations\n\n**Common Billing Issues:**\n- Duplicate charges: Usually resolved within 24 hours\n- Failed payments: Update payment method in settings\n- Upgrade/downgrade: Changes take effect immediately\n\n**Contacting Billing:**\nFor billing questions, include your account email and approximate charge date.",
|
35
|
+
"tags": ["billing", "refunds", "charges", "payment"],
|
36
|
+
"created_at": "2023-08-20T14:00:00Z",
|
37
|
+
"updated_at": "2024-01-08T10:30:00Z"
|
38
|
+
}
|
39
|
+
},
|
40
|
+
"categories": [
|
41
|
+
{
|
42
|
+
"name": "troubleshooting",
|
43
|
+
"description": "Technical issues and problem resolution"
|
44
|
+
},
|
45
|
+
{
|
46
|
+
"name": "account",
|
47
|
+
"description": "Account management and login issues"
|
48
|
+
},
|
49
|
+
{
|
50
|
+
"name": "development",
|
51
|
+
"description": "API documentation and developer resources"
|
52
|
+
},
|
53
|
+
{
|
54
|
+
"name": "billing",
|
55
|
+
"description": "Billing, payments, and subscription management"
|
56
|
+
}
|
57
|
+
]
|
58
|
+
}
|
@@ -0,0 +1,83 @@
|
|
1
|
+
{
|
2
|
+
"issues": {
|
3
|
+
"ENG-445": {
|
4
|
+
"id": "ENG-445",
|
5
|
+
"title": "API /users endpoint returning 500 errors intermittently",
|
6
|
+
"status": "in_progress",
|
7
|
+
"priority": "high",
|
8
|
+
"assignee": "alex.dev@company.com",
|
9
|
+
"labels": ["bug", "api", "production"],
|
10
|
+
"created_at": "2024-01-12T11:15:00Z",
|
11
|
+
"updated_at": "2024-01-12T15:30:00Z",
|
12
|
+
"description": "Customer reports 20% failure rate on /users endpoint with 500 errors. Investigation shows database connection pool exhaustion during peak traffic.",
|
13
|
+
"comments": [
|
14
|
+
{
|
15
|
+
"author": "alex.dev@company.com",
|
16
|
+
"content": "Reproduced locally. Issue appears to be connection pool size too small for current load.",
|
17
|
+
"timestamp": "2024-01-12T13:20:00Z"
|
18
|
+
},
|
19
|
+
{
|
20
|
+
"author": "sarah.lead@company.com",
|
21
|
+
"content": "Increasing pool size from 10 to 25 connections. Testing in staging.",
|
22
|
+
"timestamp": "2024-01-12T15:30:00Z"
|
23
|
+
}
|
24
|
+
]
|
25
|
+
},
|
26
|
+
"ENG-442": {
|
27
|
+
"id": "ENG-442",
|
28
|
+
"title": "Add dark mode support to web application",
|
29
|
+
"status": "backlog",
|
30
|
+
"priority": "medium",
|
31
|
+
"assignee": null,
|
32
|
+
"labels": ["feature", "ui", "accessibility"],
|
33
|
+
"created_at": "2024-01-05T10:00:00Z",
|
34
|
+
"updated_at": "2024-01-08T14:15:00Z",
|
35
|
+
"description": "Multiple customers have requested dark mode. This would improve accessibility and user experience, especially for users who work in low-light environments.",
|
36
|
+
"comments": [
|
37
|
+
{
|
38
|
+
"author": "product.manager@company.com",
|
39
|
+
"content": "Added to Q1 roadmap. Design team will create mockups first.",
|
40
|
+
"timestamp": "2024-01-08T14:15:00Z"
|
41
|
+
}
|
42
|
+
]
|
43
|
+
},
|
44
|
+
"ENG-440": {
|
45
|
+
"id": "ENG-440",
|
46
|
+
"title": "Login page occasionally shows white screen",
|
47
|
+
"status": "resolved",
|
48
|
+
"priority": "medium",
|
49
|
+
"assignee": "mike.frontend@company.com",
|
50
|
+
"labels": ["bug", "frontend", "login"],
|
51
|
+
"created_at": "2024-01-03T09:30:00Z",
|
52
|
+
"updated_at": "2024-01-10T16:00:00Z",
|
53
|
+
"description": "Some users report seeing a white screen when navigating to login page. Appears to be related to JavaScript bundle loading issues.",
|
54
|
+
"resolution": "Fixed race condition in bundle loading. Deployed fix in v2.1.3",
|
55
|
+
"comments": [
|
56
|
+
{
|
57
|
+
"author": "mike.frontend@company.com",
|
58
|
+
"content": "Issue was caused by missing error boundary. Added proper loading states.",
|
59
|
+
"timestamp": "2024-01-10T16:00:00Z"
|
60
|
+
}
|
61
|
+
]
|
62
|
+
},
|
63
|
+
"ENG-438": {
|
64
|
+
"id": "ENG-438",
|
65
|
+
"title": "Implement password strength indicator",
|
66
|
+
"status": "completed",
|
67
|
+
"priority": "low",
|
68
|
+
"assignee": "jane.security@company.com",
|
69
|
+
"labels": ["security", "enhancement", "password"],
|
70
|
+
"created_at": "2024-01-01T11:00:00Z",
|
71
|
+
"updated_at": "2024-01-07T13:45:00Z",
|
72
|
+
"description": "Add real-time password strength indicator to signup and password reset forms to help users create secure passwords.",
|
73
|
+
"resolution": "Implemented using zxcvbn library. Shows strength meter and helpful suggestions.",
|
74
|
+
"comments": [
|
75
|
+
{
|
76
|
+
"author": "jane.security@company.com",
|
77
|
+
"content": "Added strength meter with color coding and actionable feedback.",
|
78
|
+
"timestamp": "2024-01-07T13:45:00Z"
|
79
|
+
}
|
80
|
+
]
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
@@ -0,0 +1,71 @@
|
|
1
|
+
{
|
2
|
+
"john.smith@example.com": {
|
3
|
+
"customer_id": "cus_123abc",
|
4
|
+
"subscription_status": "active",
|
5
|
+
"current_plan": "Pro Plan",
|
6
|
+
"monthly_amount": 2999,
|
7
|
+
"currency": "usd",
|
8
|
+
"next_billing_date": "2024-02-15",
|
9
|
+
"payment_method": "card_ending_4242",
|
10
|
+
"recent_charges": [
|
11
|
+
{
|
12
|
+
"date": "2024-01-15",
|
13
|
+
"amount": 2999,
|
14
|
+
"status": "succeeded",
|
15
|
+
"description": "Pro Plan subscription"
|
16
|
+
},
|
17
|
+
{
|
18
|
+
"date": "2024-01-10",
|
19
|
+
"amount": 2999,
|
20
|
+
"status": "failed",
|
21
|
+
"description": "Pro Plan subscription - duplicate charge refunded"
|
22
|
+
}
|
23
|
+
],
|
24
|
+
"total_paid": 89970,
|
25
|
+
"customer_since": "2023-12-01"
|
26
|
+
},
|
27
|
+
"sarah.j@startup.io": {
|
28
|
+
"customer_id": "cus_456def",
|
29
|
+
"subscription_status": "active",
|
30
|
+
"current_plan": "Basic Plan",
|
31
|
+
"monthly_amount": 999,
|
32
|
+
"currency": "usd",
|
33
|
+
"next_billing_date": "2024-02-01",
|
34
|
+
"payment_method": "card_ending_1234",
|
35
|
+
"recent_charges": [
|
36
|
+
{
|
37
|
+
"date": "2024-01-01",
|
38
|
+
"amount": 999,
|
39
|
+
"status": "succeeded",
|
40
|
+
"description": "Basic Plan subscription"
|
41
|
+
}
|
42
|
+
],
|
43
|
+
"total_paid": 999,
|
44
|
+
"customer_since": "2024-01-01"
|
45
|
+
},
|
46
|
+
"mike.chen@techfirm.com": {
|
47
|
+
"customer_id": "cus_789ghi",
|
48
|
+
"subscription_status": "active",
|
49
|
+
"current_plan": "Enterprise Plan",
|
50
|
+
"monthly_amount": 9999,
|
51
|
+
"currency": "usd",
|
52
|
+
"next_billing_date": "2024-02-15",
|
53
|
+
"payment_method": "card_ending_5678",
|
54
|
+
"recent_charges": [
|
55
|
+
{
|
56
|
+
"date": "2024-01-15",
|
57
|
+
"amount": 9999,
|
58
|
+
"status": "succeeded",
|
59
|
+
"description": "Enterprise Plan subscription"
|
60
|
+
},
|
61
|
+
{
|
62
|
+
"date": "2023-12-15",
|
63
|
+
"amount": 9999,
|
64
|
+
"status": "succeeded",
|
65
|
+
"description": "Enterprise Plan subscription"
|
66
|
+
}
|
67
|
+
],
|
68
|
+
"total_paid": 79992,
|
69
|
+
"customer_since": "2023-06-15"
|
70
|
+
}
|
71
|
+
}
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../../lib/agents"
|
5
|
+
require_relative "agents/copilot_orchestrator"
|
6
|
+
|
7
|
+
# Configure the agents SDK
|
8
|
+
Agents.configure do |config|
|
9
|
+
config.openai_api_key = ENV["OPENAI_API_KEY"]
|
10
|
+
config.default_model = "gpt-4o-mini"
|
11
|
+
config.debug = false
|
12
|
+
end
|
13
|
+
|
14
|
+
puts "=== Support Copilot Demo ==="
|
15
|
+
puts "This demonstrates agent-as-tool collaboration where specialist agents work behind the scenes."
|
16
|
+
puts
|
17
|
+
|
18
|
+
# Create the main copilot
|
19
|
+
copilot = Copilot::CopilotOrchestrator.create
|
20
|
+
runner = Agents::Runner.with_agents(copilot)
|
21
|
+
context = {}
|
22
|
+
|
23
|
+
# Demo scenarios
|
24
|
+
# scenarios = [
|
25
|
+
# {
|
26
|
+
# title: "API Errors - Enterprise Customer",
|
27
|
+
# query: "mike.chen@techfirm.com reporting API 500 errors (CONV-004). Help?"
|
28
|
+
# },
|
29
|
+
# {
|
30
|
+
# title: "Angry Customer - Login + Billing",
|
31
|
+
# query: "john.smith@example.com can't login AND has billing issues (CONV-001). Threatening to cancel. What do I do?"
|
32
|
+
# },
|
33
|
+
# {
|
34
|
+
# title: "Dark Mode Feature Request",
|
35
|
+
# query: "Customer asking about dark mode. Is this being worked on? Should I create a ticket?"
|
36
|
+
# },
|
37
|
+
# {
|
38
|
+
# title: "Enterprise Integration Issues",
|
39
|
+
# query: "CONTACT-789 can't get API integration working. Need help with response."
|
40
|
+
# }
|
41
|
+
# ]
|
42
|
+
|
43
|
+
# scenarios.each_with_index do |scenario, i|
|
44
|
+
# puts("-" * 60)
|
45
|
+
# puts "Scenario #{i + 1}: #{scenario[:title]}"
|
46
|
+
# puts("-" * 60)
|
47
|
+
# puts "Support Agent Query: #{scenario[:query]}"
|
48
|
+
# puts
|
49
|
+
# puts "Copilot Response:"
|
50
|
+
# puts
|
51
|
+
|
52
|
+
# begin
|
53
|
+
# result = runner.run(scenario[:query])
|
54
|
+
# puts result.output
|
55
|
+
# rescue StandardError => e
|
56
|
+
# puts "Error: #{e.message}"
|
57
|
+
# end
|
58
|
+
|
59
|
+
# puts
|
60
|
+
# puts "Press Enter to continue to next scenario..."
|
61
|
+
# gets
|
62
|
+
# puts
|
63
|
+
# end
|
64
|
+
|
65
|
+
puts "=== Interactive Mode ==="
|
66
|
+
puts "Now you can ask the copilot questions directly."
|
67
|
+
puts "Type 'exit' to quit."
|
68
|
+
puts
|
69
|
+
|
70
|
+
loop do
|
71
|
+
print "Support Agent: "
|
72
|
+
input = gets.chomp
|
73
|
+
break if input.downcase == "exit"
|
74
|
+
|
75
|
+
puts
|
76
|
+
puts "Copilot:"
|
77
|
+
begin
|
78
|
+
result = runner.run(input, context: context)
|
79
|
+
|
80
|
+
# Update context with the returned context from Runner
|
81
|
+
context = result.context if result.respond_to?(:context) && result.context
|
82
|
+
|
83
|
+
puts result.output
|
84
|
+
rescue StandardError => e
|
85
|
+
puts "Error: #{e.message}"
|
86
|
+
end
|
87
|
+
puts
|
88
|
+
end
|
89
|
+
|
90
|
+
puts "Thanks for trying the Support Copilot demo!"
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Copilot
|
6
|
+
# Tool for creating Linear tickets for engineering issues
|
7
|
+
class CreateLinearTicketTool < Agents::Tool
|
8
|
+
description "Create a Linear ticket for engineering issues or feature requests"
|
9
|
+
param :title, type: "string", desc: "Title of the issue"
|
10
|
+
param :description, type: "string", desc: "Detailed description of the issue"
|
11
|
+
param :priority, type: "string", desc: "Priority level (low, medium, high)"
|
12
|
+
param :assignee, type: "string", desc: "Optional: email of person to assign to", required: false
|
13
|
+
param :labels, type: "string", desc: "Comma-separated labels (e.g., bug,api,production)"
|
14
|
+
|
15
|
+
def perform(tool_context, title:, description:, priority: "medium", assignee: nil, labels: "")
|
16
|
+
# Generate a ticket ID
|
17
|
+
ticket_id = "ENG-#{rand(100..999)}"
|
18
|
+
|
19
|
+
# Parse labels
|
20
|
+
label_array = labels.split(",").map(&:strip).reject(&:empty?)
|
21
|
+
label_array = ["support-request"] if label_array.empty?
|
22
|
+
|
23
|
+
# Create ticket data
|
24
|
+
{
|
25
|
+
id: ticket_id,
|
26
|
+
title: title,
|
27
|
+
description: description,
|
28
|
+
status: "backlog",
|
29
|
+
priority: priority.downcase,
|
30
|
+
assignee: assignee,
|
31
|
+
labels: label_array,
|
32
|
+
created_at: Time.now.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
33
|
+
reporter: "support-agent"
|
34
|
+
}
|
35
|
+
|
36
|
+
# Store ticket info in shared state
|
37
|
+
tool_context.state[:created_ticket_id] = ticket_id
|
38
|
+
tool_context.state[:ticket_priority] = priority
|
39
|
+
tool_context.state[:ticket_assignee] = assignee
|
40
|
+
|
41
|
+
response = {
|
42
|
+
success: true,
|
43
|
+
ticket_id: ticket_id,
|
44
|
+
title: title,
|
45
|
+
status: "backlog",
|
46
|
+
priority: priority,
|
47
|
+
assignee: assignee,
|
48
|
+
labels: label_array,
|
49
|
+
url: "https://linear.app/company/issue/#{ticket_id}",
|
50
|
+
message: "Successfully created Linear ticket #{ticket_id}"
|
51
|
+
}
|
52
|
+
|
53
|
+
JSON.pretty_generate(response)
|
54
|
+
rescue StandardError => e
|
55
|
+
"Error creating Linear ticket: #{e.message}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Copilot
|
6
|
+
# Tool for retrieving specific knowledge base articles by ID
|
7
|
+
class GetArticleTool < Agents::Tool
|
8
|
+
description "Get the full content of a specific knowledge base article"
|
9
|
+
param :article_id, type: "string", desc: "ID of the article to retrieve (e.g., ART-001)"
|
10
|
+
|
11
|
+
def perform(tool_context, article_id:)
|
12
|
+
data_file = File.join(__dir__, "../data/knowledge_base.json")
|
13
|
+
return "Knowledge base unavailable" unless File.exist?(data_file)
|
14
|
+
|
15
|
+
begin
|
16
|
+
kb_data = JSON.parse(File.read(data_file))
|
17
|
+
article = kb_data["articles"][article_id.upcase]
|
18
|
+
|
19
|
+
return "Article not found" unless article
|
20
|
+
|
21
|
+
# Store article data in shared state for reference
|
22
|
+
tool_context.state[:last_article_id] = article_id.upcase
|
23
|
+
tool_context.state[:last_article_category] = article["category"]
|
24
|
+
|
25
|
+
response = {
|
26
|
+
id: article_id.upcase,
|
27
|
+
title: article["title"],
|
28
|
+
category: article["category"],
|
29
|
+
content: article["content"],
|
30
|
+
tags: article["tags"],
|
31
|
+
created_at: article["created_at"],
|
32
|
+
updated_at: article["updated_at"]
|
33
|
+
}
|
34
|
+
|
35
|
+
JSON.pretty_generate(response)
|
36
|
+
rescue StandardError => e
|
37
|
+
"Error retrieving article: #{e.message}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Copilot
|
6
|
+
# Tool for retrieving contact/customer information
|
7
|
+
class GetContactTool < Agents::Tool
|
8
|
+
description "Get customer profile and contact information"
|
9
|
+
param :contact_id, type: "string", desc: "ID of the contact to retrieve"
|
10
|
+
|
11
|
+
def perform(tool_context, contact_id:)
|
12
|
+
data_file = File.join(__dir__, "../data/contacts.json")
|
13
|
+
return "Contacts database unavailable" unless File.exist?(data_file)
|
14
|
+
|
15
|
+
begin
|
16
|
+
contacts = JSON.parse(File.read(data_file))
|
17
|
+
contact = contacts[contact_id.upcase]
|
18
|
+
|
19
|
+
return "Contact not found" unless contact
|
20
|
+
|
21
|
+
# Store contact data in shared state for other agents
|
22
|
+
tool_context.state[:contact_name] = contact["name"]
|
23
|
+
tool_context.state[:contact_email] = contact["email"]
|
24
|
+
tool_context.state[:contact_company] = contact["company"]
|
25
|
+
tool_context.state[:contact_plan] = contact["plan"]
|
26
|
+
tool_context.state[:contact_satisfaction] = contact["satisfaction_score"]
|
27
|
+
|
28
|
+
# Format contact information
|
29
|
+
response = {
|
30
|
+
id: contact["id"],
|
31
|
+
name: contact["name"],
|
32
|
+
email: contact["email"],
|
33
|
+
phone: contact["phone"],
|
34
|
+
company: contact["company"],
|
35
|
+
plan: contact["plan"],
|
36
|
+
account_status: contact["account_status"],
|
37
|
+
created_at: contact["created_at"],
|
38
|
+
last_login: contact["last_login"],
|
39
|
+
total_conversations: contact["total_conversations"],
|
40
|
+
satisfaction_score: contact["satisfaction_score"],
|
41
|
+
tags: contact["tags"],
|
42
|
+
notes: contact["notes"]
|
43
|
+
}
|
44
|
+
|
45
|
+
JSON.pretty_generate(response)
|
46
|
+
rescue StandardError => e
|
47
|
+
"Error retrieving contact: #{e.message}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Copilot
|
6
|
+
# Tool for retrieving conversation details and context
|
7
|
+
class GetConversationTool < Agents::Tool
|
8
|
+
description "Get conversation details, messages, and context for analysis"
|
9
|
+
param :conversation_id, type: "string", desc: "ID of the conversation to retrieve"
|
10
|
+
|
11
|
+
def perform(tool_context, conversation_id:)
|
12
|
+
data_file = File.join(__dir__, "../data/conversations.json")
|
13
|
+
return "Conversations database unavailable" unless File.exist?(data_file)
|
14
|
+
|
15
|
+
begin
|
16
|
+
conversations = JSON.parse(File.read(data_file))
|
17
|
+
conversation = conversations[conversation_id.upcase]
|
18
|
+
|
19
|
+
return "Conversation not found" unless conversation
|
20
|
+
|
21
|
+
# Store conversation data in shared state for other agents
|
22
|
+
tool_context.state[:current_conversation_id] = conversation_id.upcase
|
23
|
+
tool_context.state[:conversation_status] = conversation["status"]
|
24
|
+
tool_context.state[:conversation_subject] = conversation["subject"]
|
25
|
+
tool_context.state[:contact_id] = conversation["contact_id"]
|
26
|
+
|
27
|
+
# Format conversation for analysis
|
28
|
+
formatted_messages = conversation["messages"].map do |msg|
|
29
|
+
"#{msg["role"].upcase} (#{msg["timestamp"]}): #{msg["content"]}"
|
30
|
+
end
|
31
|
+
|
32
|
+
response = {
|
33
|
+
id: conversation["id"],
|
34
|
+
subject: conversation["subject"],
|
35
|
+
status: conversation["status"],
|
36
|
+
contact_id: conversation["contact_id"],
|
37
|
+
created_at: conversation["created_at"],
|
38
|
+
message_count: conversation["messages"].length,
|
39
|
+
tags: conversation["tags"],
|
40
|
+
messages: formatted_messages
|
41
|
+
}
|
42
|
+
|
43
|
+
# Add resolution if available
|
44
|
+
response[:resolution] = conversation["resolution"] if conversation["resolution"]
|
45
|
+
response[:escalation_ticket] = conversation["escalation_ticket"] if conversation["escalation_ticket"]
|
46
|
+
|
47
|
+
JSON.pretty_generate(response)
|
48
|
+
rescue StandardError => e
|
49
|
+
"Error retrieving conversation: #{e.message}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Copilot
|
6
|
+
# Tool for retrieving Stripe billing information
|
7
|
+
class GetStripeBillingTool < Agents::Tool
|
8
|
+
description "Get customer billing information and payment history from Stripe"
|
9
|
+
param :customer_email, type: "string", desc: "Customer email to look up billing info"
|
10
|
+
|
11
|
+
def perform(tool_context, customer_email:)
|
12
|
+
data_file = File.join(__dir__, "../data/stripe_billing.json")
|
13
|
+
return "Stripe billing data unavailable" unless File.exist?(data_file)
|
14
|
+
|
15
|
+
begin
|
16
|
+
billing_data = JSON.parse(File.read(data_file))
|
17
|
+
customer_data = billing_data[customer_email.downcase]
|
18
|
+
|
19
|
+
return "No billing information found for #{customer_email}" unless customer_data
|
20
|
+
|
21
|
+
# Store billing info in shared state
|
22
|
+
tool_context.state[:stripe_customer_id] = customer_data["customer_id"]
|
23
|
+
tool_context.state[:subscription_status] = customer_data["subscription_status"]
|
24
|
+
tool_context.state[:current_plan] = customer_data["current_plan"]
|
25
|
+
|
26
|
+
formatted_data = {
|
27
|
+
customer_email: customer_email,
|
28
|
+
stripe_customer_id: customer_data["customer_id"],
|
29
|
+
subscription_status: customer_data["subscription_status"],
|
30
|
+
current_plan: customer_data["current_plan"],
|
31
|
+
monthly_amount: "$#{customer_data["monthly_amount"] / 100}",
|
32
|
+
next_billing_date: customer_data["next_billing_date"],
|
33
|
+
payment_method: customer_data["payment_method"],
|
34
|
+
total_paid: "$#{customer_data["total_paid"] / 100}",
|
35
|
+
recent_charges: customer_data["recent_charges"]
|
36
|
+
}
|
37
|
+
|
38
|
+
JSON.pretty_generate(formatted_data)
|
39
|
+
rescue StandardError => e
|
40
|
+
"Error retrieving billing information: #{e.message}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Copilot
|
6
|
+
# Tool for searching contacts to find patterns and related customers
|
7
|
+
class SearchContactsTool < Agents::Tool
|
8
|
+
description "Search contacts to find patterns, related customers, or specific profiles"
|
9
|
+
param :query, type: "string", desc: "Search terms (name, email, company, or tags)"
|
10
|
+
param :plan, type: "string", desc: "Optional: filter by plan type (Basic, Pro, Enterprise)", required: false
|
11
|
+
|
12
|
+
def perform(_tool_context, query:, plan: nil)
|
13
|
+
data_file = File.join(__dir__, "../data/contacts.json")
|
14
|
+
return "Contacts database unavailable" unless File.exist?(data_file)
|
15
|
+
|
16
|
+
begin
|
17
|
+
contacts = JSON.parse(File.read(data_file))
|
18
|
+
query_text = query.downcase
|
19
|
+
matches = []
|
20
|
+
|
21
|
+
contacts.each do |contact_id, contact|
|
22
|
+
# Skip if plan filter is specified and doesn't match
|
23
|
+
next if plan && contact["plan"].downcase != plan.downcase
|
24
|
+
|
25
|
+
# Simple keyword matching
|
26
|
+
text_to_search = [
|
27
|
+
contact["name"],
|
28
|
+
contact["email"],
|
29
|
+
contact["company"],
|
30
|
+
contact["tags"].join(" "),
|
31
|
+
contact["notes"]
|
32
|
+
].join(" ").downcase
|
33
|
+
|
34
|
+
next unless text_to_search.include?(query_text)
|
35
|
+
|
36
|
+
matches << {
|
37
|
+
contact_id: contact_id,
|
38
|
+
name: contact["name"],
|
39
|
+
email: contact["email"],
|
40
|
+
company: contact["company"],
|
41
|
+
plan: contact["plan"],
|
42
|
+
tags: contact["tags"],
|
43
|
+
satisfaction_score: contact["satisfaction_score"]
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
if matches.empty?
|
48
|
+
"No contacts found for: #{query}"
|
49
|
+
else
|
50
|
+
JSON.pretty_generate({ query: query, contacts: matches })
|
51
|
+
end
|
52
|
+
rescue StandardError => e
|
53
|
+
"Error searching contacts: #{e.message}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Copilot
|
6
|
+
# Tool for searching through conversation history to find similar cases
|
7
|
+
class SearchConversationsTool < Agents::Tool
|
8
|
+
description "Search past conversations for similar cases and resolutions"
|
9
|
+
param :query, type: "string", desc: "Search terms (keywords, tags, or issue description)"
|
10
|
+
param :contact_id, type: "string", desc: "Optional: limit search to specific contact", required: false
|
11
|
+
|
12
|
+
def perform(_tool_context, query:, contact_id: nil)
|
13
|
+
data_file = File.join(__dir__, "../data/conversations.json")
|
14
|
+
return "Conversations database unavailable" unless File.exist?(data_file)
|
15
|
+
|
16
|
+
begin
|
17
|
+
conversations = JSON.parse(File.read(data_file))
|
18
|
+
query_text = query.downcase
|
19
|
+
matches = []
|
20
|
+
|
21
|
+
conversations.each do |conv_id, conversation|
|
22
|
+
# Skip if contact_id filter is specified and doesn't match
|
23
|
+
next if contact_id && conversation["contact_id"] != contact_id.upcase
|
24
|
+
|
25
|
+
# Simple keyword matching
|
26
|
+
text_to_search = [
|
27
|
+
conversation["subject"],
|
28
|
+
conversation["tags"].join(" "),
|
29
|
+
conversation["messages"].map { |m| m["content"] }.join(" "),
|
30
|
+
conversation["resolution"]
|
31
|
+
].compact.join(" ").downcase
|
32
|
+
|
33
|
+
next unless text_to_search.include?(query_text)
|
34
|
+
|
35
|
+
matches << {
|
36
|
+
conversation_id: conv_id,
|
37
|
+
subject: conversation["subject"],
|
38
|
+
status: conversation["status"],
|
39
|
+
tags: conversation["tags"],
|
40
|
+
resolution: conversation["resolution"]
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
if matches.empty?
|
45
|
+
"No similar conversations found for: #{query}"
|
46
|
+
else
|
47
|
+
JSON.pretty_generate({ query: query, conversations: matches })
|
48
|
+
end
|
49
|
+
rescue StandardError => e
|
50
|
+
"Error searching conversations: #{e.message}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|