tavus 0.1.0
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 +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +32 -0
- data/CHANGELOG.md +58 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +90 -0
- data/LICENSE.txt +21 -0
- data/README.md +736 -0
- data/Rakefile +153 -0
- data/examples/advanced_persona.rb +148 -0
- data/examples/basic_usage.rb +107 -0
- data/examples/complete_workflow.rb +200 -0
- data/lib/tavus/client.rb +150 -0
- data/lib/tavus/configuration.rb +26 -0
- data/lib/tavus/errors.rb +13 -0
- data/lib/tavus/resources/conversations.rb +66 -0
- data/lib/tavus/resources/documents.rb +61 -0
- data/lib/tavus/resources/guardrails.rb +88 -0
- data/lib/tavus/resources/objectives.rb +87 -0
- data/lib/tavus/resources/personas.rb +97 -0
- data/lib/tavus/resources/replicas.rb +64 -0
- data/lib/tavus/resources/videos.rb +90 -0
- data/lib/tavus/version.rb +5 -0
- data/lib/tavus.rb +31 -0
- metadata +168 -0
data/lib/tavus/client.rb
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Tavus
|
|
7
|
+
class Client
|
|
8
|
+
attr_reader :configuration
|
|
9
|
+
|
|
10
|
+
def initialize(api_key: nil, base_url: nil, timeout: nil)
|
|
11
|
+
@configuration = Configuration.new
|
|
12
|
+
@configuration.api_key = api_key || Tavus.configuration.api_key
|
|
13
|
+
@configuration.base_url = base_url || Tavus.configuration.base_url
|
|
14
|
+
@configuration.timeout = timeout || Tavus.configuration.timeout
|
|
15
|
+
|
|
16
|
+
validate_configuration!
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def conversations
|
|
20
|
+
@conversations ||= Resources::Conversations.new(self)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def personas
|
|
24
|
+
@personas ||= Resources::Personas.new(self)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def replicas
|
|
28
|
+
@replicas ||= Resources::Replicas.new(self)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def objectives
|
|
32
|
+
@objectives ||= Resources::Objectives.new(self)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def guardrails
|
|
36
|
+
@guardrails ||= Resources::Guardrails.new(self)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def documents
|
|
40
|
+
@documents ||= Resources::Documents.new(self)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def videos
|
|
44
|
+
@videos ||= Resources::Videos.new(self)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def get(path, params: {})
|
|
48
|
+
request(:get, path, params: params)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def post(path, body: {}, params: {})
|
|
52
|
+
request(:post, path, body: body, params: params)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def patch(path, body: {}, params: {})
|
|
56
|
+
request(:patch, path, body: body, params: params)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def delete(path, params: {})
|
|
60
|
+
request(:delete, path, params: params)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def validate_configuration!
|
|
66
|
+
raise ConfigurationError, "API key is required" unless configuration.valid?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def request(method, path, body: {}, params: {})
|
|
70
|
+
uri = build_uri(path, params)
|
|
71
|
+
http = build_http(uri)
|
|
72
|
+
request = build_request(method, uri, body)
|
|
73
|
+
|
|
74
|
+
response = http.request(request)
|
|
75
|
+
handle_response(response)
|
|
76
|
+
rescue StandardError => e
|
|
77
|
+
raise ApiError, "Request failed: #{e.message}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def build_uri(path, params)
|
|
81
|
+
uri = configuration.base_uri.dup
|
|
82
|
+
uri.path = path
|
|
83
|
+
uri.query = URI.encode_www_form(params) unless params.empty?
|
|
84
|
+
uri
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def build_http(uri)
|
|
88
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
89
|
+
http.use_ssl = uri.scheme == "https"
|
|
90
|
+
http.read_timeout = configuration.timeout
|
|
91
|
+
http.open_timeout = configuration.timeout
|
|
92
|
+
http
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def build_request(method, uri, body)
|
|
96
|
+
request_class = case method
|
|
97
|
+
when :get then Net::HTTP::Get
|
|
98
|
+
when :post then Net::HTTP::Post
|
|
99
|
+
when :patch then Net::HTTP::Patch
|
|
100
|
+
when :delete then Net::HTTP::Delete
|
|
101
|
+
else raise ArgumentError, "Unsupported HTTP method: #{method}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
request = request_class.new(uri)
|
|
105
|
+
request["x-api-key"] = configuration.api_key
|
|
106
|
+
request["Content-Type"] = "application/json"
|
|
107
|
+
request["Accept"] = "application/json"
|
|
108
|
+
|
|
109
|
+
if [:post, :patch].include?(method) && !body.empty?
|
|
110
|
+
request.body = JSON.generate(body)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
request
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def handle_response(response)
|
|
117
|
+
case response.code.to_i
|
|
118
|
+
when 200, 201
|
|
119
|
+
parse_json_response(response)
|
|
120
|
+
when 204
|
|
121
|
+
{ success: true }
|
|
122
|
+
when 400
|
|
123
|
+
error = parse_json_response(response)
|
|
124
|
+
raise BadRequestError, error["error"] || error["message"] || "Bad request"
|
|
125
|
+
when 401
|
|
126
|
+
error = parse_json_response(response)
|
|
127
|
+
raise AuthenticationError, error["message"] || "Invalid access token"
|
|
128
|
+
when 404
|
|
129
|
+
raise NotFoundError, "Resource not found"
|
|
130
|
+
when 422
|
|
131
|
+
error = parse_json_response(response)
|
|
132
|
+
raise ValidationError, error["error"] || error["message"] || "Validation failed"
|
|
133
|
+
when 429
|
|
134
|
+
raise RateLimitError, "Rate limit exceeded"
|
|
135
|
+
when 500..599
|
|
136
|
+
raise ServerError, "Server error: #{response.code}"
|
|
137
|
+
else
|
|
138
|
+
raise ApiError, "Unexpected response: #{response.code}"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def parse_json_response(response)
|
|
143
|
+
return {} if response.body.nil? || response.body.empty?
|
|
144
|
+
|
|
145
|
+
JSON.parse(response.body)
|
|
146
|
+
rescue JSON::ParserError
|
|
147
|
+
{ "raw_body" => response.body }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module Tavus
|
|
6
|
+
class Configuration
|
|
7
|
+
attr_accessor :api_key, :base_url, :timeout
|
|
8
|
+
|
|
9
|
+
DEFAULT_BASE_URL = "https://tavusapi.com"
|
|
10
|
+
DEFAULT_TIMEOUT = 30
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@api_key = nil
|
|
14
|
+
@base_url = DEFAULT_BASE_URL
|
|
15
|
+
@timeout = DEFAULT_TIMEOUT
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def valid?
|
|
19
|
+
!api_key.nil? && !api_key.empty?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def base_uri
|
|
23
|
+
URI.parse(base_url)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/tavus/errors.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tavus
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
class ConfigurationError < Error; end
|
|
6
|
+
class ApiError < Error; end
|
|
7
|
+
class AuthenticationError < ApiError; end
|
|
8
|
+
class BadRequestError < ApiError; end
|
|
9
|
+
class NotFoundError < ApiError; end
|
|
10
|
+
class ValidationError < ApiError; end
|
|
11
|
+
class RateLimitError < ApiError; end
|
|
12
|
+
class ServerError < ApiError; end
|
|
13
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tavus
|
|
4
|
+
module Resources
|
|
5
|
+
class Conversations
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Create a new conversation
|
|
11
|
+
# @param replica_id [String] The unique identifier for the replica
|
|
12
|
+
# @param persona_id [String] The unique identifier for the persona
|
|
13
|
+
# @param options [Hash] Additional optional parameters
|
|
14
|
+
# @option options [Boolean] :audio_only Specifies whether the interaction should be voice-only
|
|
15
|
+
# @option options [String] :callback_url A URL that will receive webhooks
|
|
16
|
+
# @option options [String] :conversation_name A name for the conversation
|
|
17
|
+
# @option options [String] :conversational_context Optional context for the conversation
|
|
18
|
+
# @option options [String] :custom_greeting Custom greeting for the replica
|
|
19
|
+
# @option options [Array<String>] :memory_stores Memory stores to use for the conversation
|
|
20
|
+
# @option options [Array<String>] :document_ids Document IDs the persona can access
|
|
21
|
+
# @option options [String] :document_retrieval_strategy Strategy for document retrieval (speed, quality, balanced)
|
|
22
|
+
# @option options [Array<String>] :document_tags Document tags the replica can access
|
|
23
|
+
# @option options [Boolean] :test_mode If true, conversation created but replica won't join
|
|
24
|
+
# @option options [Hash] :properties Optional properties to customize the conversation
|
|
25
|
+
# @return [Hash] The created conversation details
|
|
26
|
+
def create(replica_id: nil, persona_id: nil, **options)
|
|
27
|
+
body = options.dup
|
|
28
|
+
body[:replica_id] = replica_id if replica_id
|
|
29
|
+
body[:persona_id] = persona_id if persona_id
|
|
30
|
+
|
|
31
|
+
@client.post("/v2/conversations", body: body)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Get a single conversation by ID
|
|
35
|
+
# @param conversation_id [String] The unique identifier of the conversation
|
|
36
|
+
# @return [Hash] The conversation details
|
|
37
|
+
def get(conversation_id)
|
|
38
|
+
@client.get("/v2/conversations/#{conversation_id}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# List all conversations
|
|
42
|
+
# @param options [Hash] Query parameters
|
|
43
|
+
# @option options [Integer] :limit Number of conversations to return per page (default: 10)
|
|
44
|
+
# @option options [Integer] :page Page number to return (default: 1)
|
|
45
|
+
# @option options [String] :status Filter by status (active, ended)
|
|
46
|
+
# @return [Hash] List of conversations and total count
|
|
47
|
+
def list(**options)
|
|
48
|
+
@client.get("/v2/conversations", params: options)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# End a conversation
|
|
52
|
+
# @param conversation_id [String] The unique identifier of the conversation
|
|
53
|
+
# @return [Hash] Success response
|
|
54
|
+
def end(conversation_id)
|
|
55
|
+
@client.post("/v2/conversations/#{conversation_id}/end")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Delete a conversation
|
|
59
|
+
# @param conversation_id [String] The unique identifier of the conversation
|
|
60
|
+
# @return [Hash] Success response
|
|
61
|
+
def delete(conversation_id)
|
|
62
|
+
@client.delete("/v2/conversations/#{conversation_id}")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tavus
|
|
4
|
+
module Resources
|
|
5
|
+
class Documents
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Create a new document in the knowledge base
|
|
11
|
+
# @param document_url [String] The URL of the document to be processed (required)
|
|
12
|
+
# @param options [Hash] Additional optional parameters
|
|
13
|
+
# @option options [String] :document_name Optional name for the document
|
|
14
|
+
# @option options [String] :callback_url Optional URL for status updates
|
|
15
|
+
# @option options [Hash] :properties Optional key-value pairs for additional properties
|
|
16
|
+
# @option options [Array<String>] :tags Optional array of tags to categorize the document
|
|
17
|
+
# @return [Hash] The created document details
|
|
18
|
+
def create(document_url:, **options)
|
|
19
|
+
body = options.merge(document_url: document_url)
|
|
20
|
+
@client.post("/v2/documents", body: body)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Get a single document by ID
|
|
24
|
+
# @param document_id [String] The unique identifier of the document
|
|
25
|
+
# @return [Hash] The document details
|
|
26
|
+
def get(document_id)
|
|
27
|
+
@client.get("/v2/documents/#{document_id}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# List all documents
|
|
31
|
+
# @param options [Hash] Query parameters
|
|
32
|
+
# @option options [Integer] :limit Number of documents to return per page (default: 10)
|
|
33
|
+
# @option options [Integer] :page Page number for pagination (0-based, default: 0)
|
|
34
|
+
# @option options [String] :sort Sort direction (ascending, descending)
|
|
35
|
+
# @option options [String] :status Filter documents by status
|
|
36
|
+
# @option options [String] :name_or_uuid Search by name or UUID
|
|
37
|
+
# @option options [String] :tags Comma-separated list of tags to filter by
|
|
38
|
+
# @return [Hash] List of documents with pagination info
|
|
39
|
+
def list(**options)
|
|
40
|
+
@client.get("/v2/documents", params: options)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Update a document's metadata
|
|
44
|
+
# @param document_id [String] The unique identifier of the document
|
|
45
|
+
# @param options [Hash] Update parameters
|
|
46
|
+
# @option options [String] :document_name New name for the document
|
|
47
|
+
# @option options [Array<String>] :tags New array of tags (overwrites existing)
|
|
48
|
+
# @return [Hash] The updated document details
|
|
49
|
+
def update(document_id, **options)
|
|
50
|
+
@client.patch("/v2/documents/#{document_id}", body: options)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Delete a document
|
|
54
|
+
# @param document_id [String] The unique identifier of the document
|
|
55
|
+
# @return [Hash] Success response
|
|
56
|
+
def delete(document_id)
|
|
57
|
+
@client.delete("/v2/documents/#{document_id}")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tavus
|
|
4
|
+
module Resources
|
|
5
|
+
class Guardrails
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Create new guardrails
|
|
11
|
+
# @param name [String] Descriptive name for the collection of guardrails
|
|
12
|
+
# @param data [Array<Hash>] Array of individual guardrails
|
|
13
|
+
# @return [Hash] The created guardrails details
|
|
14
|
+
def create(name: nil, data: [])
|
|
15
|
+
body = {}
|
|
16
|
+
body[:name] = name if name
|
|
17
|
+
body[:data] = data unless data.empty?
|
|
18
|
+
|
|
19
|
+
@client.post("/v2/guardrails", body: body)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Get a single set of guardrails by ID
|
|
23
|
+
# @param guardrails_id [String] The unique identifier of the guardrails
|
|
24
|
+
# @return [Hash] The guardrails details
|
|
25
|
+
def get(guardrails_id)
|
|
26
|
+
@client.get("/v2/guardrails/#{guardrails_id}")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# List all guardrails
|
|
30
|
+
# @param options [Hash] Query parameters
|
|
31
|
+
# @option options [Integer] :limit Number of guardrails to return per page (default: 10)
|
|
32
|
+
# @option options [Integer] :page Page number to return (default: 1)
|
|
33
|
+
# @return [Hash] List of guardrails and total count
|
|
34
|
+
def list(**options)
|
|
35
|
+
@client.get("/v2/guardrails", params: options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Update guardrails using JSON Patch operations
|
|
39
|
+
# @param guardrails_id [String] The unique identifier of the guardrails
|
|
40
|
+
# @param operations [Array<Hash>] Array of JSON Patch operations
|
|
41
|
+
# @example
|
|
42
|
+
# operations = [
|
|
43
|
+
# { op: "replace", path: "/data/0/guardrails_prompt", value: "Your updated prompt" },
|
|
44
|
+
# { op: "add", path: "/data/0/callback_url", value: "https://your-server.com/webhook" }
|
|
45
|
+
# ]
|
|
46
|
+
# @return [Hash] Success response
|
|
47
|
+
def patch(guardrails_id, operations)
|
|
48
|
+
raise ArgumentError, "Operations must be an array" unless operations.is_a?(Array)
|
|
49
|
+
raise ArgumentError, "Operations cannot be empty" if operations.empty?
|
|
50
|
+
|
|
51
|
+
operations.each do |op|
|
|
52
|
+
raise ArgumentError, "Each operation must have 'op' and 'path'" unless op[:op] && op[:path]
|
|
53
|
+
raise ArgumentError, "Operation 'op' must be one of: add, remove, replace, copy, move, test" unless %w[add remove replace copy move test].include?(op[:op])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@client.patch("/v2/guardrails/#{guardrails_id}", body: operations)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Helper method to build patch operations
|
|
60
|
+
# @param field [String] The field path
|
|
61
|
+
# @param value [Object] The value to set
|
|
62
|
+
# @param operation [String] The operation type (default: "replace")
|
|
63
|
+
# @return [Hash] A JSON Patch operation
|
|
64
|
+
def build_patch_operation(field, value, operation: "replace")
|
|
65
|
+
op = { op: operation, path: field }
|
|
66
|
+
op[:value] = value unless operation == "remove"
|
|
67
|
+
op
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Convenient method to update a field
|
|
71
|
+
# @param guardrails_id [String] The unique identifier of the guardrails
|
|
72
|
+
# @param field [String] The field path to update
|
|
73
|
+
# @param value [Object] The new value
|
|
74
|
+
# @return [Hash] Success response
|
|
75
|
+
def update_field(guardrails_id, field, value)
|
|
76
|
+
operation = build_patch_operation(field, value, operation: "replace")
|
|
77
|
+
patch(guardrails_id, [operation])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Delete guardrails
|
|
81
|
+
# @param guardrails_id [String] The unique identifier of the guardrails
|
|
82
|
+
# @return [Hash] Success response
|
|
83
|
+
def delete(guardrails_id)
|
|
84
|
+
@client.delete("/v2/guardrails/#{guardrails_id}")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tavus
|
|
4
|
+
module Resources
|
|
5
|
+
class Objectives
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Create new objectives
|
|
11
|
+
# @param data [Array<Hash>] Array of objectives to create
|
|
12
|
+
# @return [Hash] The created objective details
|
|
13
|
+
def create(data:)
|
|
14
|
+
raise ArgumentError, "data must be an array" unless data.is_a?(Array)
|
|
15
|
+
raise ArgumentError, "data cannot be empty" if data.empty?
|
|
16
|
+
|
|
17
|
+
@client.post("/v2/objectives", body: { data: data })
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get a single objective by ID
|
|
21
|
+
# @param objectives_id [String] The unique identifier of the objective
|
|
22
|
+
# @return [Hash] The objective details
|
|
23
|
+
def get(objectives_id)
|
|
24
|
+
@client.get("/v2/objectives/#{objectives_id}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# List all objectives
|
|
28
|
+
# @param options [Hash] Query parameters
|
|
29
|
+
# @option options [Integer] :limit Number of objectives to return per page (default: 10)
|
|
30
|
+
# @option options [Integer] :page Page number to return (default: 1)
|
|
31
|
+
# @return [Hash] List of objectives and total count
|
|
32
|
+
def list(**options)
|
|
33
|
+
@client.get("/v2/objectives", params: options)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Update an objective using JSON Patch operations
|
|
37
|
+
# @param objectives_id [String] The unique identifier of the objective
|
|
38
|
+
# @param operations [Array<Hash>] Array of JSON Patch operations
|
|
39
|
+
# @example
|
|
40
|
+
# operations = [
|
|
41
|
+
# { op: "replace", path: "/data/0/objective_name", value: "updated_objective_name" },
|
|
42
|
+
# { op: "replace", path: "/data/0/objective_prompt", value: "Updated prompt" },
|
|
43
|
+
# { op: "add", path: "/data/0/output_variables", value: ["new_variable"] }
|
|
44
|
+
# ]
|
|
45
|
+
# @return [Hash] Success response
|
|
46
|
+
def patch(objectives_id, operations)
|
|
47
|
+
raise ArgumentError, "Operations must be an array" unless operations.is_a?(Array)
|
|
48
|
+
raise ArgumentError, "Operations cannot be empty" if operations.empty?
|
|
49
|
+
|
|
50
|
+
operations.each do |op|
|
|
51
|
+
raise ArgumentError, "Each operation must have 'op' and 'path'" unless op[:op] && op[:path]
|
|
52
|
+
raise ArgumentError, "Operation 'op' must be one of: add, remove, replace, copy, move, test" unless %w[add remove replace copy move test].include?(op[:op])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@client.patch("/v2/objectives/#{objectives_id}", body: operations)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Helper method to build patch operations
|
|
59
|
+
# @param field [String] The field path
|
|
60
|
+
# @param value [Object] The value to set
|
|
61
|
+
# @param operation [String] The operation type (default: "replace")
|
|
62
|
+
# @return [Hash] A JSON Patch operation
|
|
63
|
+
def build_patch_operation(field, value, operation: "replace")
|
|
64
|
+
op = { op: operation, path: field }
|
|
65
|
+
op[:value] = value unless operation == "remove"
|
|
66
|
+
op
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Convenient method to update a field
|
|
70
|
+
# @param objectives_id [String] The unique identifier of the objective
|
|
71
|
+
# @param field [String] The field path to update
|
|
72
|
+
# @param value [Object] The new value
|
|
73
|
+
# @return [Hash] Success response
|
|
74
|
+
def update_field(objectives_id, field, value)
|
|
75
|
+
operation = build_patch_operation(field, value, operation: "replace")
|
|
76
|
+
patch(objectives_id, [operation])
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Delete an objective
|
|
80
|
+
# @param objectives_id [String] The unique identifier of the objective
|
|
81
|
+
# @return [Hash] Success response
|
|
82
|
+
def delete(objectives_id)
|
|
83
|
+
@client.delete("/v2/objectives/#{objectives_id}")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tavus
|
|
4
|
+
module Resources
|
|
5
|
+
class Personas
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Create a new persona
|
|
11
|
+
# @param system_prompt [String] The system prompt for the LLM (required for full pipeline mode)
|
|
12
|
+
# @param options [Hash] Additional optional parameters
|
|
13
|
+
# @option options [String] :persona_name A name for the persona
|
|
14
|
+
# @option options [String] :pipeline_mode Pipeline mode (full, echo)
|
|
15
|
+
# @option options [String] :context Context for the LLM
|
|
16
|
+
# @option options [String] :default_replica_id Default replica ID for the persona
|
|
17
|
+
# @option options [Array<String>] :document_ids Document IDs the persona can access
|
|
18
|
+
# @option options [Array<String>] :document_tags Document tags the persona can access
|
|
19
|
+
# @option options [Hash] :layers Configuration for perception, STT, LLM, TTS layers
|
|
20
|
+
# @return [Hash] The created persona details
|
|
21
|
+
def create(system_prompt: nil, **options)
|
|
22
|
+
body = options.dup
|
|
23
|
+
body[:system_prompt] = system_prompt if system_prompt
|
|
24
|
+
|
|
25
|
+
@client.post("/v2/personas", body: body)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get a single persona by ID
|
|
29
|
+
# @param persona_id [String] The unique identifier of the persona
|
|
30
|
+
# @return [Hash] The persona details
|
|
31
|
+
def get(persona_id)
|
|
32
|
+
@client.get("/v2/personas/#{persona_id}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# List all personas
|
|
36
|
+
# @param options [Hash] Query parameters
|
|
37
|
+
# @option options [Integer] :limit Number of personas to return per page (default: 10)
|
|
38
|
+
# @option options [Integer] :page Page number to return (default: 1)
|
|
39
|
+
# @option options [String] :persona_type Filter by type (user, system)
|
|
40
|
+
# @return [Hash] List of personas and total count
|
|
41
|
+
def list(**options)
|
|
42
|
+
@client.get("/v2/personas", params: options)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Update a persona using JSON Patch operations
|
|
46
|
+
# @param persona_id [String] The unique identifier of the persona
|
|
47
|
+
# @param operations [Array<Hash>] Array of JSON Patch operations
|
|
48
|
+
# @example
|
|
49
|
+
# operations = [
|
|
50
|
+
# { op: "replace", path: "/persona_name", value: "Wellness Advisor" },
|
|
51
|
+
# { op: "add", path: "/layers/tts/tts_emotion_control", value: "true" },
|
|
52
|
+
# { op: "remove", path: "/layers/stt/hotwords" }
|
|
53
|
+
# ]
|
|
54
|
+
# @return [Hash] Success response
|
|
55
|
+
def patch(persona_id, operations)
|
|
56
|
+
raise ArgumentError, "Operations must be an array" unless operations.is_a?(Array)
|
|
57
|
+
raise ArgumentError, "Operations cannot be empty" if operations.empty?
|
|
58
|
+
|
|
59
|
+
# Validate each operation has required fields
|
|
60
|
+
operations.each do |op|
|
|
61
|
+
raise ArgumentError, "Each operation must have 'op' and 'path'" unless op[:op] && op[:path]
|
|
62
|
+
raise ArgumentError, "Operation 'op' must be one of: add, remove, replace, copy, move, test" unless %w[add remove replace copy move test].include?(op[:op])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
@client.patch("/v2/personas/#{persona_id}", body: operations)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Helper method to build patch operations
|
|
69
|
+
# @param field [String] The field path (e.g., "/persona_name", "/layers/llm/model")
|
|
70
|
+
# @param value [Object] The value to set
|
|
71
|
+
# @param operation [String] The operation type (default: "replace")
|
|
72
|
+
# @return [Hash] A JSON Patch operation
|
|
73
|
+
def build_patch_operation(field, value, operation: "replace")
|
|
74
|
+
op = { op: operation, path: field }
|
|
75
|
+
op[:value] = value unless operation == "remove"
|
|
76
|
+
op
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Convenient method to replace a field
|
|
80
|
+
# @param persona_id [String] The unique identifier of the persona
|
|
81
|
+
# @param field [String] The field path to update
|
|
82
|
+
# @param value [Object] The new value
|
|
83
|
+
# @return [Hash] Success response
|
|
84
|
+
def update_field(persona_id, field, value)
|
|
85
|
+
operation = build_patch_operation(field, value, operation: "replace")
|
|
86
|
+
patch(persona_id, [operation])
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Delete a persona
|
|
90
|
+
# @param persona_id [String] The unique identifier of the persona
|
|
91
|
+
# @return [Hash] Success response
|
|
92
|
+
def delete(persona_id)
|
|
93
|
+
@client.delete("/v2/personas/#{persona_id}")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tavus
|
|
4
|
+
module Resources
|
|
5
|
+
class Replicas
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Create a new replica
|
|
11
|
+
# @param train_video_url [String] Direct link to training video (required)
|
|
12
|
+
# @param options [Hash] Additional optional parameters
|
|
13
|
+
# @option options [String] :consent_video_url Direct link to consent video
|
|
14
|
+
# @option options [String] :callback_url URL to receive callbacks on completion
|
|
15
|
+
# @option options [String] :replica_name Name for the replica
|
|
16
|
+
# @option options [String] :model_name Phoenix model version (default: phoenix-3)
|
|
17
|
+
# @option options [Hash] :properties Additional properties
|
|
18
|
+
# @return [Hash] The created replica details
|
|
19
|
+
def create(train_video_url:, **options)
|
|
20
|
+
body = options.merge(train_video_url: train_video_url)
|
|
21
|
+
@client.post("/v2/replicas", body: body)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Get a single replica by ID
|
|
25
|
+
# @param replica_id [String] The unique identifier of the replica
|
|
26
|
+
# @param verbose [Boolean] Include additional replica data (default: false)
|
|
27
|
+
# @return [Hash] The replica details
|
|
28
|
+
def get(replica_id, verbose: false)
|
|
29
|
+
params = verbose ? { verbose: true } : {}
|
|
30
|
+
@client.get("/v2/replicas/#{replica_id}", params: params)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# List all replicas
|
|
34
|
+
# @param options [Hash] Query parameters
|
|
35
|
+
# @option options [Integer] :limit Number of replicas to return per page
|
|
36
|
+
# @option options [Integer] :page Page number to return
|
|
37
|
+
# @option options [Boolean] :verbose Include additional replica data
|
|
38
|
+
# @option options [String] :replica_type Filter by type (user, system)
|
|
39
|
+
# @option options [String] :replica_ids Comma-separated list of replica IDs to filter
|
|
40
|
+
# @return [Hash] List of replicas and total count
|
|
41
|
+
def list(**options)
|
|
42
|
+
@client.get("/v2/replicas", params: options)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Delete a replica
|
|
46
|
+
# @param replica_id [String] The unique identifier of the replica
|
|
47
|
+
# @param hard [Boolean] If true, hard delete replica and assets (irreversible)
|
|
48
|
+
# @return [Hash] Success response
|
|
49
|
+
def delete(replica_id, hard: false)
|
|
50
|
+
params = hard ? { hard: true } : {}
|
|
51
|
+
@client.delete("/v2/replicas/#{replica_id}", params: params)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Rename a replica
|
|
55
|
+
# @param replica_id [String] The unique identifier of the replica
|
|
56
|
+
# @param replica_name [String] New name for the replica
|
|
57
|
+
# @return [Hash] Success response
|
|
58
|
+
def rename(replica_id, replica_name)
|
|
59
|
+
@client.patch("/v2/replicas/#{replica_id}/name", body: { replica_name: replica_name })
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|