
Overview: Ollama Unplugged with Personality
The Ollama Unplugged with Personality application is a dynamic chatbot framework that allows users to interact with an AI enriched with customizable traits and preferences. This app stands out by enabling users to configure the AI's personality, tone, expertise, and more, resulting in personalized and engaging conversations.
Key Components of the Application
- Chat Panel (Left Section)
- Response Display: Shows the AI's responses in a conversational format.
- Input Box: Enter your query or message for the AI at the bottom.
- "Get Response" Button: Submits your input to the AI and fetches its response.
- Model Configuration Panel (Right Section): This section is divided into categories for customizing the AI's behavior.
- Identity
- Name: Assign a name to your AI.
- Model Selection: Choose the AI model version to use.
- Fact-Checking Options
- Enabled or Disabled: Determines whether the AI prioritizes factual accuracy.
- Demographic
- Gender: Choose between Male or Female.
- Language: Select the language for communication.
- Expertise and Role
- Field of Expertise: Define the domain knowledge of the AI (e.g., Sports, Science, etc.).
- Role: Set the AI’s conversational role (e.g., Critic, Listener, Expert).
- Interaction Style
- Politeness Level: Adjust how polite or direct the AI is during conversations.
- Personality Traits
- Personality: Define the AI’s overall demeanor (e.g., Aggressive, Friendly).
- Conversation Style: Choose how the AI approaches dialogue (e.g., Inquisitive Mind, Jokester).
- Tone: Set the emotional tone of responses (e.g., Sarcastic, Formal).
- Response Preferences
- Detail Level: Control how detailed the AI’s responses are (e.g., Moderate, Comprehensive).
- Response Length: Adjust the length of responses (e.g., Short, Normal, Long).
- Creativity Level: Fine-tune how imaginative the responses should be (e.g., Balanced, High).
- IO (Input/Output)
- Configurations: Save or load AI configurations to easily switch between profiles.
- Identity
Introduction
While I was learning how to integrate AI into my C# apps, my colleague introduced me to Ollama and its AI models. Naturally, I couldn’t resist giving it a shot. I built my first app, and while it worked, something felt... off. The AI was smart, but its responses were a bit too robotic—like it just graduated from "AI University" with a degree in Blandness.
That’s when it hit me: What if AI responses were as quirky, sarcastic, or insightful as a conversation with your favorite eccentric friend? And so, "Ollama Unplugged with Personality" was born—a C# app that lets you give your AI some much-needed personality. Want a jokester bot? Done. A philosopher? You got it. An AI that speaks like a tech guru? No problem. How about a sports commentator with a rough, critical edge who’ll analyze your every move like you’re in the finals? Absolutely! Let’s dive into the chaos—I mean, creativity!
Before diving into the fun of creating "Ollama Unplugged with Personality," make sure you have the following ready.
- Basic C# Knowledge: You should be comfortable with writing simple C# code, working with classes, and understanding basic programming concepts.
- Visual Studio Installed: Download and install Visual Studio. The free Community edition works perfectly.
- .NET Core SDK (Version 8.0 or Higher): Ensure you have the latest .NET Core SDK installed. You can download it from here.
- Ollama AI API
- Set up the Ollama API on your local machine: You’ll need it for your app to communicate with the AI models.
- Download Ollama: Visit Ollama's official website and download the setup for your platform.
- Install Ollama: Run the installer and follow the on-screen instructions.
- Run Ollama Locally: Open a terminal and type Ollama to start initializing the local API.
- Install a Model: Use the command Ollama pull <model_name> (e.g., Ollama pull llama3.2) to download at least one AI model. Ensure it’s running so your app works.
- Enthusiasm for Humor and Creativity: Seriously, this one’s important. Be ready to laugh at some of the quirky responses your AI might come up with!
Setting Up the API
To make your AI app work, you need a way to communicate with the AI model hosted on the Ollama API. This is where the ApiCommunicationHandler class comes into play. It simplifies sending requests and processing responses, so you can focus on making your AI shine.
This class handles everything related to talking to the API.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
namespace Ollamas_Unplugged_Personality.Api
{
/// <summary>
/// Handles communication with an external API
/// for sending requests and processing responses.
/// </summary>
public class ApiCommunicationHandler : IApiCommunicationHandler
{
private readonly string _baseUrl;
private static readonly HttpClient _httpClient = new();
/// <summary>
/// Initializes a new instance
/// of the <see cref="ApiCommunicationHandler"/>
/// class with a specified base URL.
/// </summary>
/// <param name="baseUrl">The base URL for the API.</param>
public ApiCommunicationHandler(string baseUrl)
{
_baseUrl = baseUrl;
}
/// <summary>
/// Sends a request to the API with the specified prompt and model.
/// </summary>
/// <param name="prompt">The input prompt for the API request.</param>
/// <param name="model">The model to use for the request.</param>
/// <returns>A task that represents the asynchronous operation,
/// with the response string as the result.</returns>
public async Task<string> SendRequestAsync(string prompt, string model)
{
// Set request headers to accept JSON responses
_httpClient.DefaultRequestHeaders.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Create the message payload
var message = new { model, prompt };
string jsonMessage = JsonSerializer.Serialize(message);
// Create HTTP content for the request
StringContent content = new StringContent(jsonMessage, Encoding.UTF8, "application/json");
try
{
// Send the POST request to the API endpoint
HttpResponseMessage response = await _httpClient.PostAsync(_baseUrl, content);
// Ensure the request was successful
response.EnsureSuccessStatusCode();
// Read and process the response content
string responseContent = await response.Content.ReadAsStringAsync();
return ExtractResponse(responseContent);
}
catch (HttpRequestException ex)
{
// Handle communication errors
return $"Error communicating with the model: {ex.Message}";
}
}
/// <summary>
/// Extracts the response content from a JSON response string.
/// Handles cases with multiple JSON objects separated by line breaks.
/// </summary>
/// <param name="jsonResponse">The JSON response string.</param>
/// <returns>The extracted response text or an error message if parsing fails.</returns>
private string ExtractResponse(string jsonResponse)
{
try
{
// Split the response into individual lines to handle multiple JSON objects
var jsonLines = jsonResponse.Split(
new[] { '\n', '\r' },
StringSplitOptions.RemoveEmptyEntries
);
var fullResponse = new StringBuilder();
// Process each line of the JSON response
foreach (var jsonLine in jsonLines)
{
try
{
using (JsonDocument doc = JsonDocument.Parse(jsonLine))
{
// Extract the "response" property if available
if (doc.RootElement.TryGetProperty("response", out var responseElement))
{
var responseText = responseElement.GetString();
if (!string.IsNullOrWhiteSpace(responseText))
{
fullResponse.Append(responseText);
}
}
}
}
catch (JsonException ex)
{
// Log parsing errors and continue with other lines
Console.WriteLine($"Error parsing JSON line: {ex.Message}");
continue;
}
}
// Return the full response or a default message if no response was found
return fullResponse.Length > 0
? fullResponse.ToString()
: "No response.";
}
catch (JsonException ex)
{
// Handle errors during response parsing
return $"Error parsing response: {ex.Message}";
}
}
}
}
- Base URL
- When you create an instance of ApiCommunicationHandler, you specify the base URL of the API (e.g., http://localhost:11434/api/generate).
- This ensures all requests go to the right endpoint.
- Sending Requests
- The SendRequestAsync method sends a request with your AI model and input prompt.
- It formats the data as JSON and sends it using an HTTP POST request.
- Processing Responses
- Once the API sends a response, the ExtractResponse method processes the JSON data and extracts the AI’s reply.
- It handles multiple JSON objects and skips any invalid entries.
- Error Handling: The class gracefully handles errors, such as connection issues or invalid responses, and provides clear error messages.
Here’s how you can set up and use this class in your app.
- Initialize the Handler: Create an instance of ApiCommunicationHandler with the base URL of your Ollama API.
var apiHandler = new ApiCommunicationHandler("http://localhost:11434/api/generate"); - Send a Request: Call the SendRequestAsync method with your input prompt and selected model.
string prompt = "What is the capital of France?"; string model = "llama3.2"; string response = await apiHandler.SendRequestAsync(prompt, model); Console.WriteLine(response);
Creating Dynamic AI with the OllamaEntity Class
The OllamaEntity class is the heart of "Ollama Unplugged with Personality." It connects to the API, builds custom prompts, keeps track of conversation history, and evolves the AI’s behavior based on past interactions.
What Does the OllamaEntity Class Do?
This class acts as a bridge between your app and the AI model with the following key features.
- Customizable AI Behavior: Using properties like Personality, Tone, and CreativityLevel, you can define how your AI responds.
- Dynamic Conversations: The class evolves the AI's state based on past interactions, adjusting tone, creativity, and response length.
- Context-Aware Responses: It maintains a conversation history, allowing the AI to respond in a way that feels natural and continuous.
using Ollamas_Unplugged_Personality.Api;
using Ollamas_Unplugged_Personality.Enums;
using Ollamas_Unplugged_Personality.Extensions;
using Ollamas_Unplugged_Personality.Model;
namespace Ollamas_Unplugged_Personality.Entity
{
/// <summary>
/// Represents an abstract base class
/// for entities interacting with the Ollamas API.
/// Provides context-aware response generation
/// and state evolution based on conversation history.
/// </summary>
public abstract class OllamaEntity : IOllamaEntity
{
#region Fields and Constants
protected readonly IApiCommunicationHandler _apiHandler;
private const int ContextWindowSize = 5;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance
/// of the <see cref="OllamaEntity"/>
/// class with the specified API communication handler.
/// </summary>
/// <param name="apiHandler">The API communication handler.</param>
protected OllamaEntity(IApiCommunicationHandler apiHandler)
{
_apiHandler = apiHandler;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the name of the entity.
/// </summary>
public string Name { get; set; } = "Ollama";
/// <summary>
/// Gets or sets the model used for generating responses.
/// </summary>
public string Model { get; set; } = "llama3.2";
/// <summary>
/// Gets the last response generated by the entity.
/// </summary>
public string LastResponse { get; private set; } = string.Empty;
/// <summary>
/// Gets or sets a custom prompt used for generating responses.
/// </summary>
public string CustomPrompt { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether fact-checking is enabled.
/// </summary>
public bool FactCheckingEnabled { get; set; } = false;
/// <summary>
/// Gets the conversation history as a list of conversation entries.
/// </summary>
public List<ConversationEntry> ConversationHistory { get; private set; } = new List<ConversationEntry>();
// Personality and conversation-related properties
public Personality Personality { get; set; } = Personality.Neutral;
public Gender Gender { get; set; } = Gender.Male;
public Language Language { get; set; } = Language.English;
public Role Role { get; set; } = Role.Expert;
public FieldOfExpertise FieldOfExpertise { get; set; } = FieldOfExpertise.General;
public ResponseLength ResponseLength { get; set; } = ResponseLength.Normal;
public Tone Tone { get; set; } = Tone.Neutral;
public CreativityLevel CreativityLevel { get; set; } = CreativityLevel.Balanced;
public DetailLevel DetailLevel { get; set; } = DetailLevel.Moderate;
public PolitenessLevel PolitenessLevel { get; set; } = PolitenessLevel.Neutral;
public ConversationStyle ConversationStyle { get; set; } = ConversationStyle.Listener;
#endregion
#region Public Methods
/// <summary>
/// Generates a response based on the provided input.
/// </summary>
/// <param name="input">The input string.</param>
/// <param name="cancellation">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation,
/// with the generated response as the result.</returns>
public virtual async Task<string> GenerateResponse(string input,
CancellationToken cancellation = default)
{
if (string.IsNullOrWhiteSpace(input))
return string.Empty;
// Build prompt with context
string prompt = BuildPrompt(input);
string response = await _apiHandler.SendRequestAsync(prompt, Model);
LastResponse = response;
// Store interaction in history
ConversationHistory.Add(new ConversationEntry
{
Input = input,
Response = response,
Timestamp = DateTime.UtcNow
});
// Trigger state evolution based on context
EvolveBasedOnContext();
SummarizeContextIfNecessary();
return response;
}
/// <summary>
/// Generates a personal profile summary of the entity.
/// </summary>
/// <returns>A string representing the personal profile.</returns>
public string GeneratePersonalProfile()
{
return $"You are {Name}, " +
$"and you identify as a {Gender.GetDisplayName()}. \n" +
$"Your role is a {Role.GetDisplayName()} " +
$"with a {Personality.GetDisplayName()} personality. \n" +
$"Your conversation style is: {ConversationStyle.GetDisplayName()}. \n" +
$"Your primary focus is on the topic: {FieldOfExpertise.GetDisplayName()}. \n" +
$"You communicate in {Language.GetDisplayName()} " +
$"and maintain a {Tone.GetDisplayName()} tone. \n" +
$"Your response length preference is {ResponseLength.GetDisplayName()}, " +
$"detail level: {DetailLevel.GetDisplayName()}, \n" +
$"creativity level: {CreativityLevel.GetDisplayName()}, " +
$"and politeness level: {PolitenessLevel.GetDisplayName()}. \n" +
$"Fact-checking is {(FactCheckingEnabled ? "enabled" : "disabled")}.";
}
#endregion
#region Protected Methods
/// <summary>
/// Builds a prompt for generating a response based on the provided input.
/// </summary>
/// <param name="input">The input string.</param>
/// <returns>The generated prompt string.</returns>
protected virtual string BuildPrompt(string input)
{
// Incorporate recent context
var recentHistorySnippet = string.Join("\n", GetRecentContext()
.Select(e => $"[INPUT: {e.Input}]\n[RESPONSE: {e.Response}]"));
CustomPrompt = "Give response without introducing yourself";
var prompt =
$"{recentHistorySnippet}\n" +
$"Your name is {Name}. " +
$"You are a {Role} with a {Personality} personality. " +
$"Tone: {Tone}, focused on {FieldOfExpertise}. " +
$"Provide a response with {ResponseLength} length, " +
$"{DetailLevel} detail, " +
$"and in a {PolitenessLevel} tone. " +
$"Use {Language}. " +
$"Creativity level: {CreativityLevel}. " +
$"You identify as {Gender}. " +
$"{CustomPrompt}. " +
$"[INPUT: {input}]";
if (FactCheckingEnabled)
{
prompt += " Ensure factual accuracy.";
}
return prompt;
}
#endregion
#region Private Methods
/// <summary>
/// Retrieves the recent conversation context for building prompts.
/// </summary>
/// <returns>An enumerable of recent conversation entries.</returns>
private IEnumerable<ConversationEntry> GetRecentContext()
{
return ConversationHistory.TakeLast(ContextWindowSize);
}
/// <summary>
/// Evolves the state of the entity based on the conversation context.
/// Adjusts tone, response length,
/// and creativity level based on keyword sentiment and sentence count.
/// </summary>
private void EvolveBasedOnContext()
{
// Adjust tone based on sentiment in conversation history
AdjustToneBasedOnSentiment();
// Only adjust response length if not explicitly set to Long via the UI
if (ResponseLength == ResponseLength.Normal)
{
// Adjust response length based on the number of sentences in responses
AdjustResponseLengthBasedOnSentenceCount();
}
// Increase creativity level if the conversation history is very long
if (ConversationHistory.Count > 20)
{
CreativityLevel = CreativityLevel.High;
}
}
/// <summary>
/// Analyzes the conversation history
/// for sentiment keywords and adjusts the tone accordingly.
/// </summary>
private void AdjustToneBasedOnSentiment()
{
var positivityKeywords =
new[] { "great", "amazing", "enjoy", "happy", "exciting" };
var negativityKeywords =
new[] { "boring", "dull", "stale", "disappointing", "bad" };
var neutralKeywords =
new[] { "okay", "fine", "average", "so-so" };
int positivityScore = ConversationHistory.Count(entry =>
positivityKeywords.Any(keyword =>
entry.Response.Contains(keyword, StringComparison.OrdinalIgnoreCase)
)
);
int negativityScore = ConversationHistory.Count(entry =>
negativityKeywords.Any(keyword =>
entry.Response.Contains(keyword, StringComparison.OrdinalIgnoreCase)
)
);
int neutralScore = ConversationHistory.Count(entry =>
neutralKeywords.Any(keyword =>
entry.Response.Contains(keyword, StringComparison.OrdinalIgnoreCase)
)
);
if (positivityScore > negativityScore && positivityScore > neutralScore)
{
Tone = Tone.Optimistic;
}
else if (negativityScore > positivityScore && negativityScore > neutralScore)
{
Tone = Tone.Pessimistic;
}
else if (neutralScore > positivityScore && neutralScore > negativityScore)
{
Tone = Tone.Neutral;
}
}
/// <summary>
/// Adjusts the response length
/// based on the number of sentences in the last response.
/// </summary>
private void AdjustResponseLengthBasedOnSentenceCount()
{
if (ConversationHistory.LastOrDefault() is { } lastEntry)
{
var sentenceCount = lastEntry.Response.Split(new[] { '.', '!', '?' }).Length;
if (sentenceCount > 5)
{
ResponseLength = ResponseLength.Long;
}
else
{
ResponseLength = ResponseLength.Short;
}
}
}
/// <summary>
/// Summarizes conversation context if necessary.
/// </summary>
private void SummarizeContextIfNecessary()
{
if (ConversationHistory.Count > 15)
{
// Condense conversation context into a short summary
var summary = string.Join("\n", ConversationHistory
.TakeLast(3)
.Select(e => $"[INPUT: {e.Input}]\n[RESPONSE: {e.Response}]"));
ConversationHistory.Clear();
ConversationHistory.Add(new ConversationEntry
{
Input = "Summary",
Response = summary,
Timestamp = DateTime.UtcNow
});
}
}
#endregion
}
}
Ck NitinPosted Dec 5, 2024, 11:59 AM
I appreciate the emphasis on ethical considerations and maintaining authenticity in AI interactions.
Ck NitinPosted Dec 5, 2024, 11:58 AM
The concept of creating AI that feels "human" by integrating tone, style, and context into its responses is fascinating and highly relevant in today's AI-driven world