GPT APIs, .NET core and a rude Mancunian Slack bot
In my professional life, I'm an EM (Engineering manager) for a software product team. My role is split between delivery, people management and technical leadership.
That technical leadership has been, for the most part, fairly 'high level', since starting this team I haven't been hands on in much other than terraform, AWS and CI/CD pipelines. There's a few reasons for this, the primary one is time - I have other responsibilities in other areas - but another one is that the core programming language we use on this team is C#, .NET core 6 specifically. Thankfully, I have an excellent team of engineers who I trust to deliver on the implementation.
Now, I am familiar with C#, I have written software in it both personally and professionally in the past, but it was a long time ago and I'm very rusty. I can read it and get the gist of it, but I miss a lot of details of how to write good C#.
It's unlikely my time split will permit me to go much more hands on than I currently am, but I think it's possible that I could contribute a little more and may participate in some pairing sessions more. So to do that, I decided I wanted to brush up on my C#.
So I fell back to one of my well tested methodologies for picking up new languages or refreshing my skills on old ones: Create an IRC/slack/chat bot!
I practically learned how to program by writing IRC bots, back then in Java and a few other languages, but since then one of the ways I've managed to pick up new languages was either by writing IRC bots, or in recent years, slack bots. The reason for this is because they're a good way to exercise a variety of skills without getting too caught up on UX/UI details.
Writing irc/slack/discord/etc bots requires you to handle:
- Networking / web API / socket interfacing to connect to the IRC server/slack service.
- String parsing and manipulation
- Plugin / extensibility / interacting with 3rd party APIs (additional commands, functionality, etc)
- User / state management
All without having to worry about pixel pushing, as your entire interface with your users is text based.
Enter qbert-dotnet
In recent years, I've focused on slack bots rather than IRC bots, and thus far they have been named 'qbert', and the iterations include:
- qbert-js
- qbert-ts
- qbert-scala
- qbert-ruby
- qbert-clojure (only a very brief start, barely functional)
qbert-dotnet is the latest, and he's written in .NET core 7.
My aims for qbert-dotnet, aside from just brushing up on C# .NET core, were:
- Utilise a ready made Slack API library to get started, then potentially revisit and write my own in the future.
- Develop a simple plugin system using reflection to avoid having to manually enable plugins (mostly just an excuse to play around with reflection in .NET).
- Integrate OpenAI's GPT chat completion API
- Use OpenAI's chat completion function calling to allow it to interact with other plugins the bot has.
So far, I've achieved all but the last one here. I have yet to dedicate time to that - but it's something I'd love to do.
It has gone reasonably well, paired with Github Copilot - I've been able to get something working to a reasonable level quite quickly.
I've configured it to be rude, offensive and speak like a Mancunian using the system prompts, and I've surprisingly got it to be pretty offensive on occasions, which I won't share here.
Below are a few examples, I haven't configured it to be rude to anyone, or any group in particular, it's just a generally rude Mancunian slack bot - so blame GPT for whatever it spits out.
First of all, it's rude to me - despite configuration stating that I'm its creator.
It's also rude to other cities and people from them in the UK:
It will also infuse the code it writes with its offensive Manc attitude:
The bot has been configured to remember past conversation, so that it can hold a conversation similar to ChatGPT, but I have been having trouble with it sometimes remembering wrongly who sent what message. This is probably due to how I'm formatting the conversation history, but I can't figure out why
I feed the user messages back into the API on each request as so:
new ChatMessage(ChatRole.User, $"<{userName}>: {userMessage}")
Perhaps I need to add a system instruction to explain this message format? I'll give that a try.
Finally, my initial spike into having GPT parse Metrolink API data works okay, but it's a bit of a hack. I want to refactor it to use the Chat Completion functions as mentioned above, so that GPT itself can craft API calls rather than me doing the API call for it and feeding the JSON into the Chat Completions API for it to parse.
Experience with C# .NET (core) 7
It's been positive, the tooling is surprisingly good. I've been writing this entirely on macOS using Jetbrains Rider and the dotnet core tooling. I can't complain at all about my experience in this ecosystem for the first time in around 7 years.
The language itself, I've always found to be good, Microsoft's overly verbose and sometimes overwrought library / API design does get a bit grating sometimes, but even that has improved very significant since I was last in this world.
It's hard to fault it really, it's in a good place, with good tooling and works seamlessly across platforms (windows, macOS and Linux) - sometime that wasn't entirely true last time I was here.