Skip to content

Commit 9a1676d

Browse files
authored
feat: add AI (#7)
* feat: add dependify AI
1 parent 2bfa312 commit 9a1676d

36 files changed

+1590
-395
lines changed

.vscode/launch.json

+10-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,16 @@
1919
"request": "launch",
2020
"preLaunchTask": "build",
2121
"program": "${workspaceFolder}/src/Dependify.Cli/bin/Debug/net8.0/Dependify.Cli.dll",
22-
"args": ["serve", "C:\\Users\\Oleksii_Nikiforov\\dev\\dependify\\samples\\aspire-project"],
22+
"args": [
23+
"serve",
24+
"C:\\Users\\Oleksii_Nikiforov\\dev\\dependify\\samples\\aspire-project",
25+
"--endpoint",
26+
"http://localhost:60759/",
27+
"--model-id",
28+
"phi3:mini",
29+
"--api-key",
30+
"apiKey"
31+
],
2332
"cwd": "${workspaceFolder}",
2433
"stopAtEntry": false,
2534
"console": "externalTerminal",

README.md

+37
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ You will see something like the following output in the terminal.
5151

5252
- Workbench ⚙️
5353
- Dependency Explorer 🔎
54+
- Chat (AI) 🤖
5455

5556
Workbench gives you high level overview of the dependencies in the solution.
5657

@@ -66,6 +67,18 @@ Dependency Explorer allows you to select the dependencies you want to see.
6667
<video src="https://github.com/user-attachments/assets/555df3ef-b0c3-4354-911f-81d4dfd07607" controls="controls">
6768
</video>
6869

70+
Chat (AI) allows you to ask questions about the dependencies.
71+
72+
```bash
73+
dependify serve $dev/cap-aspire/ \
74+
--endpoint https://api.openai.azure.com/ \
75+
--deployment-name gpt-4o-mini \
76+
--api-key <api-key>
77+
```
78+
79+
<video src="https://github.com/user-attachments/assets/b07a8b53-d3d2-4ef8-9a8c-8c3dbd865350" controls="controls">
80+
</video>
81+
6982
### Aspire support
7083

7184
You can add `Dependify.Web` as resource to your Aspire project.
@@ -249,3 +262,27 @@ var subgraph = graph.SubGraph(n => n.Id.Contains("AwesomeProjectName"));
249262
`dotnet tool install --global --add-source ./Artefacts Dependify.Cli --prerelease`
250263

251264
`dotnet tool uninstall Dependify.Cli -g`
265+
266+
```bash
267+
dotnet watch run --project ./src/Dependify.Cli/ -- \
268+
serve $dev/cap-aspire/ \
269+
--endpoint "http://localhost:1234/v1/chat/completions" \
270+
--model-id "LM Studio Community/Meta-Llama-3-8B-Instruct-GGUF" \
271+
--api-key "lm-studio" \
272+
--log-level "Information"
273+
```
274+
275+
```bash
276+
dotnet watch run --project ./src/Dependify.Cli/ -- \
277+
serve $dev/cap-aspire/ \
278+
--endpoint "" \
279+
--deployment-name "gpt-35-turbo" \
280+
--api-key "" \
281+
--log-level "Information"
282+
```
283+
Set the API key for the AppHost with the following command:
284+
285+
```bash
286+
dotnet user-secrets set "Parameters:api-key" "<api-key>"
287+
dotnet user-secrets set "Parameters:endpoint" "<endpoint>"
288+
```

assets/dependify-ai-chat-demo.mp4

68.7 MB
Binary file not shown.

dependify.sln

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "src\Web\Web.csproj",
1717
EndProject
1818
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dependify.Aspire.Hosting", "src\Dependify.Aspire.Hosting\Dependify.Aspire.Hosting.csproj", "{7A8ED9D9-0609-495F-9D36-AF696BCAC5D0}"
1919
EndProject
20+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dependify.Aspire.Hosting.Ollama", "src\Dependify.Aspire.Hosting.Ollama\Dependify.Aspire.Hosting.Ollama.csproj", "{F3BBEFB8-D9D2-4602-917B-2D7B0F1CCC8C}"
21+
EndProject
2022
Global
2123
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2224
Debug|Any CPU = Debug|Any CPU
@@ -43,6 +45,10 @@ Global
4345
{7A8ED9D9-0609-495F-9D36-AF696BCAC5D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
4446
{7A8ED9D9-0609-495F-9D36-AF696BCAC5D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
4547
{7A8ED9D9-0609-495F-9D36-AF696BCAC5D0}.Release|Any CPU.Build.0 = Release|Any CPU
48+
{F3BBEFB8-D9D2-4602-917B-2D7B0F1CCC8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49+
{F3BBEFB8-D9D2-4602-917B-2D7B0F1CCC8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
50+
{F3BBEFB8-D9D2-4602-917B-2D7B0F1CCC8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
51+
{F3BBEFB8-D9D2-4602-917B-2D7B0F1CCC8C}.Release|Any CPU.Build.0 = Release|Any CPU
4652
EndGlobalSection
4753
GlobalSection(SolutionProperties) = preSolution
4854
HideSolutionNode = FALSE
@@ -53,5 +59,6 @@ Global
5359
{A13ED5C9-227D-4C24-A04C-617A81878415} = {C3712305-26BF-4E1B-B7E3-2A603443E98F}
5460
{1358655A-56D9-45B8-80ED-758704415375} = {6EFAF0C3-2695-4C03-AAB0-D982DA582BEB}
5561
{7A8ED9D9-0609-495F-9D36-AF696BCAC5D0} = {6EFAF0C3-2695-4C03-AAB0-D982DA582BEB}
62+
{F3BBEFB8-D9D2-4602-917B-2D7B0F1CCC8C} = {6EFAF0C3-2695-4C03-AAB0-D982DA582BEB}
5663
EndGlobalSection
5764
EndGlobal
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
11
var builder = DistributedApplication.CreateBuilder(args);
22

3+
var useLocalModelParam = builder.AddParameter("use-local-model");
4+
var endpointParam = builder.AddParameter("endpoint");
5+
var deploymentNameParam = builder.AddParameter("deployment-name");
6+
var apiKeyParam = builder.AddParameter("api-key", secret: true);
7+
38
var apiService = builder.AddProject<Projects.aspire_project_ApiService>("apiservice");
49

5-
builder.AddProject<Projects.aspire_project_Web>("webfrontend")
6-
.WithExternalHttpEndpoints()
7-
.WithReference(apiService);
10+
builder.AddProject<Projects.aspire_project_Web>("webfrontend").WithExternalHttpEndpoints().WithReference(apiService);
11+
12+
var dependify = builder.AddDependify().ServeFrom("../../../");
13+
14+
if (useLocalModelParam.Resource.Value.ToString().Equals("true", StringComparison.OrdinalIgnoreCase))
15+
{
16+
var modelName = "phi3:mini";
17+
var ollama = builder.AddOllama("ollama").WithDataVolume().AddModel(modelName).WithOpenWebUI();
818

9-
builder.AddDependify("dependify1", port: 10000).WithDockerfile("..", "./aspire-project.AppHost/dependify.dockerfile");
19+
dependify.WithOpenAI(ollama, modelName);
20+
}
21+
else
22+
{
23+
// Configure the AppHost with the following command:
24+
// dotnet user-secrets set "Parameters:api-key" "<api-key>"
25+
// dotnet user-secrets set "Parameters:deployment-name" "gpt-4o-mini"
26+
// dotnet user-secrets set "Parameters:endpoint" "<endpoint>"
1027

11-
builder.AddDependify("dependify2", port: 10001).ServeFrom("../../aspire-project/");
28+
dependify.WithAzureOpenAI(
29+
endpointParam.Resource.Value.ToString(),
30+
deploymentNameParam.Resource.Value.ToString(),
31+
apiKeyParam.Resource.Value.ToString()
32+
);
33+
}
1234

1335
builder.Build().Run();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# AppHost
2+
3+
```csharp
4+
builder.AddDependify("dependify2", port: 10001).ServeFrom("../../aspire-project/");
5+
```
6+
7+
Control what is gets copied to workspace with dockerfile
8+
9+
```csharp
10+
builder.AddDependify("dependify1", port: 10000).WithDockerfile("..", "./aspire-project.AppHost/dependify.dockerfile");
11+
```

samples/aspire-project/aspire-project.AppHost/appsettings.Development.json

+6
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,11 @@
44
"Default": "Information",
55
"Microsoft.AspNetCore": "Warning"
66
}
7+
},
8+
"Parameters": {
9+
"use-local-model": true,
10+
"endpoint": "",
11+
"deployment-name": "gpt-4o-mini",
12+
"api-key": ""
713
}
814
}

samples/aspire-project/aspire-project.AppHost/aspire-project.AppHost.csproj

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
<ItemGroup>
1313
<ProjectReference Include="..\aspire-project.ApiService\aspire-project.ApiService.csproj" />
1414
<ProjectReference Include="..\aspire-project.Web\aspire-project.Web.csproj" />
15+
16+
<ProjectReference Include="..\..\..\src\Dependify.Aspire.Hosting\Dependify.Aspire.Hosting.csproj" IsAspireProjectResource="false" />
17+
<ProjectReference Include="..\..\..\src\Dependify.Aspire.Hosting.Ollama\Dependify.Aspire.Hosting.Ollama.csproj" IsAspireProjectResource="false" />
1518
</ItemGroup>
1619

1720
<ItemGroup>
1821
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.1.0" />
19-
<PackageReference Include="Dependify.Aspire.Hosting" Version="1.2.0" />
2022
</ItemGroup>
2123

2224
</Project>

samples/aspire-project/aspire-project.sln

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Microsoft Visual Studio Solution File, Format Version 12.00
1+
Microsoft Visual Studio Solution File, Format Version 12.00
22
# Visual Studio Version 17
33
VisualStudioVersion = 17.8.0.0
44
MinimumVisualStudioVersion = 17.8.0.0
@@ -10,6 +10,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "aspire-project.ApiService",
1010
EndProject
1111
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "aspire-project.Web", "aspire-project.Web\aspire-project.Web.csproj", "{AC1256AB-8F2E-46FA-B5F0-F2B0AD2FA720}"
1212
EndProject
13+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dependify.Aspire.Hosting.Ollama", "..\..\src\Dependify.Aspire.Hosting.Ollama\Dependify.Aspire.Hosting.Ollama.csproj", "{456B2C5D-AD42-4B5F-A959-9550807D1D01}"
14+
EndProject
15+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dependify.Aspire.Hosting", "..\..\src\Dependify.Aspire.Hosting\Dependify.Aspire.Hosting.csproj", "{C34E52E7-A577-46AC-B167-8139C8ED75D6}"
16+
EndProject
1317
Global
1418
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1519
Debug|Any CPU = Debug|Any CPU
@@ -32,6 +36,14 @@ Global
3236
{AC1256AB-8F2E-46FA-B5F0-F2B0AD2FA720}.Debug|Any CPU.Build.0 = Debug|Any CPU
3337
{AC1256AB-8F2E-46FA-B5F0-F2B0AD2FA720}.Release|Any CPU.ActiveCfg = Release|Any CPU
3438
{AC1256AB-8F2E-46FA-B5F0-F2B0AD2FA720}.Release|Any CPU.Build.0 = Release|Any CPU
39+
{456B2C5D-AD42-4B5F-A959-9550807D1D01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40+
{456B2C5D-AD42-4B5F-A959-9550807D1D01}.Debug|Any CPU.Build.0 = Debug|Any CPU
41+
{456B2C5D-AD42-4B5F-A959-9550807D1D01}.Release|Any CPU.ActiveCfg = Release|Any CPU
42+
{456B2C5D-AD42-4B5F-A959-9550807D1D01}.Release|Any CPU.Build.0 = Release|Any CPU
43+
{C34E52E7-A577-46AC-B167-8139C8ED75D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44+
{C34E52E7-A577-46AC-B167-8139C8ED75D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
45+
{C34E52E7-A577-46AC-B167-8139C8ED75D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
46+
{C34E52E7-A577-46AC-B167-8139C8ED75D6}.Release|Any CPU.Build.0 = Release|Any CPU
3547
EndGlobalSection
3648
GlobalSection(SolutionProperties) = preSolution
3749
HideSolutionNode = FALSE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<IsPackable>true</IsPackable>
4+
<PackageTags>aspire hosting ollama llm ai</PackageTags>
5+
<Description>Ollama support for .NET Aspire.</Description>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<PackageReference Include="Aspire.Hosting" />
9+
<PackageReference Include="OllamaSharp" />
10+
</ItemGroup>
11+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
namespace Aspire.Hosting;
2+
3+
using Aspire.Hosting.Lifecycle;
4+
using Aspire.Hosting.ApplicationModel;
5+
using Aspire.Hosting.Ollama;
6+
7+
/// <summary>
8+
/// Provides extension methods for adding Ollama to the application model.
9+
/// </summary>
10+
public static class OllamaBuilderExtensions
11+
{
12+
/// <summary>
13+
/// Adds an Ollama resource to the application. A container is used for local development.
14+
/// </summary>
15+
/// <example>
16+
/// Use in application host
17+
/// <code lang="csharp">
18+
/// var builder = DistributedApplication.CreateBuilder(args);
19+
///
20+
/// var ollama = builder.AddOllama("ollama");
21+
/// var api = builder.AddProject&lt;Projects.Api&gt;("api")
22+
/// .WithReference(ollama);
23+
///
24+
/// builder.Build().Run();
25+
/// </code>
26+
/// </example>
27+
/// <remarks>
28+
/// This version the package defaults to the 0.3.4 tag of the ollama/ollama container image.
29+
/// The .NET client library uses the http port by default to communicate and this resource exposes that endpoint.
30+
/// </remarks>
31+
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
32+
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency</param>
33+
/// <param name="enableGpu">Whether to enable GPU support.</param>
34+
/// <param name="port">The host port of the http endpoint of Ollama resource.</param>
35+
/// <returns>A reference to the <see cref="IResourceBuilder{OllamaResource}"/>.</returns>
36+
public static IResourceBuilder<OllamaResource> AddOllama(
37+
this IDistributedApplicationBuilder builder,
38+
string name,
39+
bool enableGpu = false,
40+
int? port = null
41+
)
42+
{
43+
builder.Services.TryAddLifecycleHook<OllamaLifecycleHook>();
44+
45+
var ollama = new OllamaResource(name);
46+
47+
var resource = builder
48+
.AddResource(ollama)
49+
.WithImage(OllamaContainerImageTags.Image, OllamaContainerImageTags.Tag)
50+
.WithImageRegistry(OllamaContainerImageTags.Registry)
51+
.WithHttpEndpoint(port: port, targetPort: 11434, OllamaResource.PrimaryEndpointName)
52+
.ExcludeFromManifest()
53+
.PublishAsContainer();
54+
55+
if (enableGpu)
56+
{
57+
resource = resource.WithContainerRuntimeArgs("--gpus=all");
58+
}
59+
60+
return resource;
61+
}
62+
63+
/// <summary>
64+
/// Adds a model to the Ollama resource.
65+
/// </summary>
66+
/// <example>
67+
/// Use in application host
68+
/// <code lang="csharp">
69+
/// var builder = DistributedApplication.CreateBuilder(args);
70+
///
71+
/// var ollama = builder.AddOllama("ollama");
72+
/// .AddModel("phi3")
73+
/// .WithDataVolume("ollama");
74+
///
75+
/// var api = builder.AddProject&lt;Projects.Api&gt;("api")
76+
/// .WithReference(ollama);
77+
///
78+
/// builder.Build().Run();
79+
/// </code>
80+
/// </example>
81+
/// <param name="builder">The Ollama resource builder.</param>
82+
/// <param name="modelName">The name of the model.</param>
83+
/// <remarks>This method will attempt to pull/download the model into the Ollama instance.</remarks>
84+
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
85+
public static IResourceBuilder<OllamaResource> AddModel(
86+
this IResourceBuilder<OllamaResource> builder,
87+
string modelName
88+
)
89+
{
90+
builder.Resource.AddModel(modelName);
91+
return builder;
92+
}
93+
94+
/// <summary>
95+
/// Adds a named volume for the data folder to a Ollama container resource.
96+
/// </summary>
97+
/// <param name="builder">The resource builder.</param>
98+
/// <param name="name">The name of the volume. Defaults to an auto-generated name based on the resource name.</param>
99+
/// <param name="isReadOnly">A flag that indicates if this is a read-only volume.</param>
100+
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
101+
public static IResourceBuilder<OllamaResource> WithDataVolume(
102+
this IResourceBuilder<OllamaResource> builder,
103+
string? name = null,
104+
bool isReadOnly = false
105+
) => builder.WithVolume(name ?? "ollama-aspire-data", "/root/.ollama", isReadOnly);
106+
107+
/// <summary>
108+
/// Adds a bind mount for the data folder to an Ollama container resource.
109+
/// </summary>
110+
/// <param name="builder">The resource builder.</param>
111+
/// <param name="source">The source directory on the host to mount into the container.</param>
112+
/// <param name="isReadOnly">A flag that indicates if this is a read-only mount.</param>
113+
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
114+
public static IResourceBuilder<OllamaResource> WithDataBindMount(
115+
this IResourceBuilder<OllamaResource> builder,
116+
string source,
117+
bool isReadOnly = false
118+
) => builder.WithBindMount(source, "/root/.ollama", isReadOnly);
119+
120+
/// <summary>
121+
/// Adds an administration web UI Ollama to the application model using Attu. This version the package defaults to the main tag of the Open WebUI container image
122+
/// </summary>
123+
/// <example>
124+
/// Use in application host with an Ollama resource
125+
/// <code lang="csharp">
126+
/// var builder = DistributedApplication.CreateBuilder(args);
127+
///
128+
/// var ollama = builder.AddOllama("ollama")
129+
/// .WithOpenWebUI();
130+
/// var api = builder.AddProject&lt;Projects.Api&gt;("api")
131+
/// .WithReference(ollama);
132+
///
133+
/// builder.Build().Run();
134+
/// </code>
135+
/// </example>
136+
/// <param name="builder">The Ollama resource builder.</param>
137+
/// <param name="configureContainer">Configuration callback for Open WebUI container resource.</param>
138+
/// <param name="containerName">The name of the container (Optional).</param>
139+
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
140+
/// <remarks>See https://openwebui.com for more information about Open WebUI</remarks>
141+
public static IResourceBuilder<T> WithOpenWebUI<T>(
142+
this IResourceBuilder<T> builder,
143+
Action<IResourceBuilder<OpenWebUIResource>>? configureContainer = null,
144+
string? containerName = null
145+
)
146+
where T : OllamaResource
147+
{
148+
containerName ??= $"{builder.Resource.Name}-openwebui";
149+
150+
var openWebUI = new OpenWebUIResource(containerName);
151+
var resourceBuilder = builder
152+
.ApplicationBuilder.AddResource(openWebUI)
153+
.WithImage(OllamaContainerImageTags.OpenWebUIImage, OllamaContainerImageTags.OpenWebUITag)
154+
.WithImageRegistry(OllamaContainerImageTags.OpenWebUIRegistry)
155+
.WithHttpEndpoint(targetPort: 8080, name: "http")
156+
.WithVolume("open-webui", "/app/backend/data")
157+
.WithEnvironment(context => ConfigureOpenWebUIContainer(context, builder.Resource))
158+
.ExcludeFromManifest();
159+
160+
configureContainer?.Invoke(resourceBuilder);
161+
162+
return builder;
163+
}
164+
165+
private static void ConfigureOpenWebUIContainer(EnvironmentCallbackContext context, OllamaResource resource)
166+
{
167+
context.EnvironmentVariables.Add("ENABLE_SIGNUP", "false");
168+
context.EnvironmentVariables.Add("ENABLE_COMMUNITY_SHARING", "false"); // by default don't enable sharing
169+
context.EnvironmentVariables.Add("WEBUI_AUTH", "false"); // https://docs.openwebui.com/#quick-start-with-docker--recommended
170+
context.EnvironmentVariables.Add(
171+
"OLLAMA_BASE_URL",
172+
$"{resource.PrimaryEndpoint.Scheme}://{resource.PrimaryEndpoint.ContainerHost}:{resource.PrimaryEndpoint.Port}"
173+
);
174+
}
175+
}

0 commit comments

Comments
 (0)