Skip to content

Commit 1e59bf6

Browse files
authored
Nebula AI .NET Integration (Beta) (#122)
1 parent a6afc39 commit 1e59bf6

File tree

10 files changed

+1003
-0
lines changed

10 files changed

+1003
-0
lines changed

Thirdweb.Console/Program.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Newtonsoft.Json;
99
using Newtonsoft.Json.Linq;
1010
using Thirdweb;
11+
using Thirdweb.AI;
1112
using Thirdweb.Pay;
1213

1314
DotEnv.Load();
@@ -35,6 +36,73 @@
3536

3637
#endregion
3738

39+
#region AI
40+
41+
// Prepare some context
42+
var myChain = 11155111;
43+
var myWallet = await SmartWallet.Create(personalWallet: await PrivateKeyWallet.Generate(client), chainId: myChain, gasless: true);
44+
var myContractAddress = "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8"; // DropERC1155
45+
var usdcAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238";
46+
47+
// Create a Nebula session
48+
var nebula = await ThirdwebNebula.Create(client);
49+
50+
// Chat, passing wallet context
51+
var response1 = await nebula.Chat(message: "What is my wallet address?", wallet: myWallet);
52+
Console.WriteLine($"Response 1: {response1.Message}");
53+
54+
// Chat, passing contract context
55+
var response2 = await nebula.Chat(
56+
message: "What's the total supply of token id 0 for this contract?",
57+
context: new NebulaContext(contractAddresses: new List<string> { myContractAddress }, chainIds: new List<BigInteger> { myChain })
58+
);
59+
Console.WriteLine($"Response 2: {response2.Message}");
60+
61+
// Chat, passing multiple messages and context
62+
var response3 = await nebula.Chat(
63+
messages: new List<NebulaChatMessage>
64+
{
65+
new($"Tell me the name of this contract: {myContractAddress}", NebulaChatRole.User),
66+
new("The name of the contract is CatDrop", NebulaChatRole.Assistant),
67+
new("What's the symbol of this contract?", NebulaChatRole.User),
68+
},
69+
context: new NebulaContext(contractAddresses: new List<string> { myContractAddress }, chainIds: new List<BigInteger> { myChain })
70+
);
71+
Console.WriteLine($"Response 3: {response3.Message}");
72+
73+
// Execute, this directly sends transactions
74+
var executionResult = await nebula.Execute("Approve 1 USDC to vitalik.eth", wallet: myWallet, context: new NebulaContext(contractAddresses: new List<string>() { usdcAddress }));
75+
if (executionResult.TransactionReceipts != null && executionResult.TransactionReceipts.Count > 0)
76+
{
77+
Console.WriteLine($"Receipt: {executionResult.TransactionReceipts[0]}");
78+
}
79+
else
80+
{
81+
Console.WriteLine($"Message: {executionResult.Message}");
82+
}
83+
84+
// Batch execute
85+
var batchExecutionResult = await nebula.Execute(
86+
new List<NebulaChatMessage>
87+
{
88+
new("What's the address of vitalik.eth", NebulaChatRole.User),
89+
new("The address of vitalik.eth is 0xd8dA6BF26964aF8E437eEa5e3616511D7G3a3298", NebulaChatRole.Assistant),
90+
new("Approve 1 USDC to them", NebulaChatRole.User),
91+
},
92+
wallet: myWallet,
93+
context: new NebulaContext(contractAddresses: new List<string>() { usdcAddress })
94+
);
95+
if (batchExecutionResult.TransactionReceipts != null && batchExecutionResult.TransactionReceipts.Count > 0)
96+
{
97+
Console.WriteLine($"Receipts: {JsonConvert.SerializeObject(batchExecutionResult.TransactionReceipts, Formatting.Indented)}");
98+
}
99+
else
100+
{
101+
Console.WriteLine($"Message: {batchExecutionResult.Message}");
102+
}
103+
104+
#endregion
105+
38106
#region Get Social Profiles
39107

40108
// var socialProfiles = await Utils.GetSocialProfiles(client, "joenrv.eth");
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using System.Numerics;
2+
using Thirdweb.AI;
3+
4+
namespace Thirdweb.Tests.AI;
5+
6+
public class NebulaTests : BaseTests
7+
{
8+
private const string NEBULA_TEST_CONTRACT = "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8";
9+
private const string NEBULA_TEST_USDC_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238";
10+
private const int NEBULA_TEST_CHAIN = 11155111;
11+
12+
public NebulaTests(ITestOutputHelper output)
13+
: base(output) { }
14+
15+
[Fact(Timeout = 120000)]
16+
public async Task Create_CreatesSession()
17+
{
18+
var nebula = await ThirdwebNebula.Create(this.Client);
19+
Assert.NotNull(nebula);
20+
Assert.NotNull(nebula.SessionId);
21+
}
22+
23+
[Fact(Timeout = 120000)]
24+
public async Task Create_ResumesSession()
25+
{
26+
var nebula = await ThirdwebNebula.Create(this.Client);
27+
var sessionId = nebula.SessionId;
28+
Assert.NotNull(nebula);
29+
Assert.NotNull(nebula.SessionId);
30+
31+
nebula = await ThirdwebNebula.Create(this.Client, sessionId);
32+
Assert.NotNull(nebula);
33+
Assert.Equal(sessionId, nebula.SessionId);
34+
}
35+
36+
[Fact(Timeout = 120000)]
37+
public async Task Chat_Single_ReturnsResponse()
38+
{
39+
var nebula = await ThirdwebNebula.Create(this.Client);
40+
var response = await nebula.Chat(
41+
message: "What's the symbol of this contract?",
42+
context: new NebulaContext(contractAddresses: new List<string> { NEBULA_TEST_CONTRACT }, chainIds: new List<BigInteger> { NEBULA_TEST_CHAIN })
43+
);
44+
Assert.NotNull(response);
45+
Assert.NotNull(response.Message);
46+
Assert.Contains("CAT", response.Message);
47+
}
48+
49+
[Fact(Timeout = 120000)]
50+
public async Task Chat_Single_NoContext_ReturnsResponse()
51+
{
52+
var nebula = await ThirdwebNebula.Create(this.Client);
53+
var response = await nebula.Chat(message: $"What's the symbol of this contract: {NEBULA_TEST_CONTRACT} (Sepolia)?");
54+
Assert.NotNull(response);
55+
Assert.NotNull(response.Message);
56+
Assert.Contains("CAT", response.Message);
57+
}
58+
59+
[Fact(Timeout = 120000)]
60+
public async Task Chat_Multiple_ReturnsResponse()
61+
{
62+
var nebula = await ThirdwebNebula.Create(this.Client);
63+
var response = await nebula.Chat(
64+
messages: new List<NebulaChatMessage>
65+
{
66+
new("What's the symbol of this contract?", NebulaChatRole.User),
67+
new("The symbol is CAT", NebulaChatRole.Assistant),
68+
new("What's the name of this contract?", NebulaChatRole.User),
69+
},
70+
context: new NebulaContext(contractAddresses: new List<string> { NEBULA_TEST_CONTRACT }, chainIds: new List<BigInteger> { NEBULA_TEST_CHAIN })
71+
);
72+
Assert.NotNull(response);
73+
Assert.NotNull(response.Message);
74+
Assert.Contains("CatDrop", response.Message, StringComparison.OrdinalIgnoreCase);
75+
}
76+
77+
[Fact(Timeout = 120000)]
78+
public async Task Chat_UnderstandsWalletContext()
79+
{
80+
var wallet = await PrivateKeyWallet.Generate(this.Client);
81+
var expectedAddress = await wallet.GetAddress();
82+
var nebula = await ThirdwebNebula.Create(this.Client);
83+
var response = await nebula.Chat(message: "What is my wallet address?", wallet: wallet);
84+
Assert.NotNull(response);
85+
Assert.NotNull(response.Message);
86+
Assert.Contains(expectedAddress, response.Message);
87+
}
88+
89+
[Fact(Timeout = 120000)]
90+
public async Task Execute_ReturnsMessageAndReceipt()
91+
{
92+
var signer = await PrivateKeyWallet.Generate(this.Client);
93+
var wallet = await SmartWallet.Create(signer, NEBULA_TEST_CHAIN);
94+
var nebula = await ThirdwebNebula.Create(this.Client);
95+
var response = await nebula.Execute(
96+
new List<NebulaChatMessage>
97+
{
98+
new("What's the address of vitalik.eth", NebulaChatRole.User),
99+
new("The address of vitalik.eth is 0xd8dA6BF26964aF8E437eEa5e3616511D7G3a3298", NebulaChatRole.Assistant),
100+
new("Approve 1 USDC to them", NebulaChatRole.User),
101+
},
102+
wallet: wallet,
103+
context: new NebulaContext(contractAddresses: new List<string>() { NEBULA_TEST_USDC_ADDRESS })
104+
);
105+
Assert.NotNull(response);
106+
Assert.NotNull(response.Message);
107+
Assert.NotNull(response.TransactionReceipts);
108+
Assert.NotEmpty(response.TransactionReceipts);
109+
Assert.NotNull(response.TransactionReceipts[0].TransactionHash);
110+
Assert.True(response.TransactionReceipts[0].TransactionHash.Length == 66);
111+
}
112+
113+
[Fact(Timeout = 120000)]
114+
public async Task Execute_ReturnsMessageAndReceipts()
115+
{
116+
var signer = await PrivateKeyWallet.Generate(this.Client);
117+
var wallet = await SmartWallet.Create(signer, NEBULA_TEST_CHAIN);
118+
var nebula = await ThirdwebNebula.Create(this.Client);
119+
var response = await nebula.Execute(
120+
new List<NebulaChatMessage>
121+
{
122+
new("What's the address of vitalik.eth", NebulaChatRole.User),
123+
new("The address of vitalik.eth is 0xd8dA6BF26964aF8E437eEa5e3616511D7G3a3298", NebulaChatRole.Assistant),
124+
new("Approve 1 USDC to them", NebulaChatRole.User),
125+
},
126+
wallet: wallet,
127+
context: new NebulaContext(contractAddresses: new List<string>() { NEBULA_TEST_USDC_ADDRESS })
128+
);
129+
Assert.NotNull(response);
130+
Assert.NotNull(response.Message);
131+
Assert.NotNull(response.TransactionReceipts);
132+
Assert.NotEmpty(response.TransactionReceipts);
133+
Assert.NotNull(response.TransactionReceipts[0].TransactionHash);
134+
Assert.True(response.TransactionReceipts[0].TransactionHash.Length == 66);
135+
}
136+
}

Thirdweb/Thirdweb.AI/ChatClient.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Text;
2+
using Newtonsoft.Json;
3+
4+
namespace Thirdweb.AI;
5+
6+
internal class ChatClient
7+
{
8+
private readonly IThirdwebHttpClient _httpClient;
9+
10+
public ChatClient(IThirdwebHttpClient httpClient)
11+
{
12+
this._httpClient = httpClient;
13+
}
14+
15+
public async Task<ChatResponse> SendMessageAsync(ChatParamsSingleMessage message)
16+
{
17+
var content = new StringContent(JsonConvert.SerializeObject(message), Encoding.UTF8, "application/json");
18+
var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/chat", content);
19+
_ = response.EnsureSuccessStatusCode();
20+
var responseContent = await response.Content.ReadAsStringAsync();
21+
return JsonConvert.DeserializeObject<ChatResponse>(responseContent);
22+
}
23+
24+
public async Task<ChatResponse> SendMessagesAsync(ChatParamsMultiMessages messages)
25+
{
26+
var content = new StringContent(JsonConvert.SerializeObject(messages), Encoding.UTF8, "application/json");
27+
var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/chat", content);
28+
_ = response.EnsureSuccessStatusCode();
29+
var responseContent = await response.Content.ReadAsStringAsync();
30+
return JsonConvert.DeserializeObject<ChatResponse>(responseContent);
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Text;
2+
using Newtonsoft.Json;
3+
4+
namespace Thirdweb.AI;
5+
6+
internal class ExecutionClient
7+
{
8+
private readonly IThirdwebHttpClient _httpClient;
9+
10+
public ExecutionClient(IThirdwebHttpClient httpClient)
11+
{
12+
this._httpClient = httpClient;
13+
}
14+
15+
public async Task<ChatResponse> ExecuteAsync(ChatParamsSingleMessage command)
16+
{
17+
var content = new StringContent(JsonConvert.SerializeObject(command), Encoding.UTF8, "application/json");
18+
var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/execute", content);
19+
_ = response.EnsureSuccessStatusCode();
20+
var responseContent = await response.Content.ReadAsStringAsync();
21+
return JsonConvert.DeserializeObject<ChatResponse>(responseContent);
22+
}
23+
24+
public async Task<ChatResponse> ExecuteBatchAsync(ChatParamsMultiMessages commands)
25+
{
26+
var content = new StringContent(JsonConvert.SerializeObject(commands), Encoding.UTF8, "application/json");
27+
var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/execute", content);
28+
_ = response.EnsureSuccessStatusCode();
29+
var responseContent = await response.Content.ReadAsStringAsync();
30+
return JsonConvert.DeserializeObject<ChatResponse>(responseContent);
31+
}
32+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Text;
2+
using Newtonsoft.Json;
3+
4+
namespace Thirdweb.AI;
5+
6+
internal class FeedbackClient
7+
{
8+
private readonly IThirdwebHttpClient _httpClient;
9+
10+
public FeedbackClient(IThirdwebHttpClient httpClient)
11+
{
12+
this._httpClient = httpClient;
13+
}
14+
15+
/// <summary>
16+
/// Submits feedback for a specific session and request.
17+
/// </summary>
18+
/// <param name="feedback">The feedback parameters to submit.</param>
19+
/// <returns>The submitted feedback details.</returns>
20+
public async Task<Feedback> SubmitFeedbackAsync(FeedbackParams feedback)
21+
{
22+
var content = new StringContent(JsonConvert.SerializeObject(feedback), Encoding.UTF8, "application/json");
23+
var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/feedback", content);
24+
_ = response.EnsureSuccessStatusCode();
25+
var responseContent = await response.Content.ReadAsStringAsync();
26+
return JsonConvert.DeserializeObject<ResponseModel<Feedback>>(responseContent).Result;
27+
}
28+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System.Text;
2+
using Newtonsoft.Json;
3+
4+
namespace Thirdweb.AI;
5+
6+
internal class SessionManager
7+
{
8+
private readonly IThirdwebHttpClient _httpClient;
9+
10+
public SessionManager(IThirdwebHttpClient httpClient)
11+
{
12+
this._httpClient = httpClient;
13+
}
14+
15+
public async Task<List<SessionList>> ListSessionsAsync()
16+
{
17+
var response = await this._httpClient.GetAsync($"{Constants.NEBULA_API_URL}/session/list");
18+
_ = response.EnsureSuccessStatusCode();
19+
var content = await response.Content.ReadAsStringAsync();
20+
return JsonConvert.DeserializeObject<ResponseModel<List<SessionList>>>(content).Result;
21+
}
22+
23+
public async Task<Session> GetSessionAsync(string sessionId)
24+
{
25+
var response = await this._httpClient.GetAsync($"{Constants.NEBULA_API_URL}/session/{sessionId}");
26+
_ = response.EnsureSuccessStatusCode();
27+
var content = await response.Content.ReadAsStringAsync();
28+
return JsonConvert.DeserializeObject<ResponseModel<Session>>(content).Result;
29+
}
30+
31+
public async Task<Session> CreateSessionAsync(CreateSessionParams parameters)
32+
{
33+
var content = new StringContent(JsonConvert.SerializeObject(parameters), Encoding.UTF8, "application/json");
34+
var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/session", content);
35+
_ = response.EnsureSuccessStatusCode();
36+
var responseContent = await response.Content.ReadAsStringAsync();
37+
return JsonConvert.DeserializeObject<ResponseModel<Session>>(responseContent).Result;
38+
}
39+
40+
public async Task<Session> UpdateSessionAsync(string sessionId, UpdateSessionParams parameters)
41+
{
42+
var content = new StringContent(JsonConvert.SerializeObject(parameters), Encoding.UTF8, "application/json");
43+
var response = await this._httpClient.PutAsync($"{Constants.NEBULA_API_URL}/session/{sessionId}", content);
44+
_ = response.EnsureSuccessStatusCode();
45+
var responseContent = await response.Content.ReadAsStringAsync();
46+
return JsonConvert.DeserializeObject<ResponseModel<Session>>(responseContent).Result;
47+
}
48+
49+
public async Task<SessionDeleteResponse> DeleteSessionAsync(string sessionId)
50+
{
51+
var response = await this._httpClient.DeleteAsync($"{Constants.NEBULA_API_URL}/session/{sessionId}");
52+
_ = response.EnsureSuccessStatusCode();
53+
var content = await response.Content.ReadAsStringAsync();
54+
return JsonConvert.DeserializeObject<ResponseModel<SessionDeleteResponse>>(content).Result;
55+
}
56+
57+
public async Task<Session> ClearSessionAsync(string sessionId)
58+
{
59+
var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/session/{sessionId}/clear", null);
60+
_ = response.EnsureSuccessStatusCode();
61+
var content = await response.Content.ReadAsStringAsync();
62+
return JsonConvert.DeserializeObject<ResponseModel<Session>>(content).Result;
63+
}
64+
}

0 commit comments

Comments
 (0)