# Buddy Base:Built an LLM Powered Group Chat for My Friends to Make Plans This Weekend

> Source: <https://dev.to/sanjaynep/buddy-basebuilt-an-llm-powered-group-chat-for-my-friends-to-make-plans-this-weekend-1ic2>
> Published: 2026-10-05 12:10:28+00:00

*This is a submission for the [Hacktoberfest Weekend Challenge: Build for a Friend](https://dev.to/challenges/hacktoberfest-weekend-2026-10-01)*

BuddyBase — a private, offline web application that gives your friend group a shared AI assistant. It runs entirely on your laptop. Friends connect through your local network (WiFi, Bluetooth tethering, or mobile hotspot). No data ever leaves your machine.

**Four Features, All AI-Powered**

Group Chat with AIA real-time chatroom where anyone can summon the AI by typing [@buddy](https://dev.to/buddy). It knows your group name, all members, and the last 20 messages for context. It responds like a friend who's always around — casual, funny, and actually helpful.

Decision MakerCreate a question with options. AI immediately gives a recommendation with reasoning. Everyone votes. After each vote, AI updates its recommendation considering the majority. No more 40-minute indecision loops.

Smart Shared ListsCollaborative lists where anyone can add items. Ask the AI to "suggest 5 more movies like these" or "what are we forgetting for the road trip?" It reads the list and gives real suggestions.

Personal BuddyEach friend gets a private AI assistant that learns their preferences. Tell it "I love spicy food" and next time someone asks about dinner, your buddy already knows. Memories persist in the database forever.

cannot make it in production but will be surely enhanced it and make it to production

github link : [https://github.com/sanjaynep/django-websocket](https://github.com/sanjaynep/django-websocket)

This is the core — loads Qwen once using `lru_cache`, generates responses using the same `apply_chat_template` pattern from HuggingFace, with timeout handling via `ThreadPoolExecutor`:

``` python
@lru_cache(maxsize=1)
def _load_llm():
    tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
    model = AutoModelForCausalLM.from_pretrained(
        MODEL_NAME,
        torch_dtype=torch.float32,
        device_map={"": "cpu"},
    )
    model.eval()
    return tokenizer, model

def _generate(messages):
    tokenizer, model = _load_llm()
    text = tokenizer.apply_chat_template(
        messages, tokenize=False, add_generation_prompt=True
    )
    model_inputs = tokenizer([text], return_tensors="pt").to("cpu")
    generated_ids = model.generate(**model_inputs, max_new_tokens=256, do_sample=True)
    output_ids = generated_ids[0][len(model_inputs.input_ids[0]):].tolist()
    return tokenizer.decode(output_ids, skip_special_tokens=True).strip()

def chat(messages):
    with ThreadPoolExecutor(max_workers=1) as executor:
        future = executor.submit(_generate, messages)
        return future.result(timeout=60)
```

**Key Code: WebSocket Consumer**

``` python
class GroupChatConsumer(AsyncWebsocketConsumer):
    async def receive(self, text_data):
        message = json.loads(text_data).get('message', '').strip()
        user = self.scope['user']

        chat_msg = await self._save_message(user, message, is_ai=False)
        await self.channel_layer.group_send(self.room_group_name, {
            'type': 'chat_message',
            'message': message,
            'username': user.username,
            'is_ai': False,
            'time': chat_msg.created_at.strftime('%H:%M'),
        })

        if '@buddy' in message.lower():
            await self.channel_layer.group_send(self.room_group_name, {
                'type': 'typing_indicator',
            })
            ai_response = await self._get_ai_response()
            ai_msg = await self._save_message(None, ai_response, is_ai=True)
            await self.channel_layer.group_send(self.room_group_name, {
                'type': 'chat_message',
                'message': ai_response,
                'username': 'Buddy',
                'is_ai': True,
                'time': ai_msg.created_at.strftime('%H:%M'),
            })
```

**Key Code: Preference Memory**

``` python
def _try_save_memory(user, message):
    prefixes = {
        'i love': 'loves', 'i hate': 'hates', 'i like': 'likes',
        "i don't like": 'dislikes', 'my favorite': 'favorite',
    }
    for prefix, key_prefix in prefixes.items():
        if prefix in message.lower():
            value = message[message.lower().index(prefix) + len(prefix):].strip()
            BuddyMemory.objects.update_or_create(
                user=user, key=f"{key_prefix}_{value[:20]}",
                defaults={'value': value}
            )
```

**Tech Stack**

Layer       Technology            Why

Backend     Django 4.2      Clean MVC, auth, ORM, admin panel out of the box

Real-Time   Django Channels + WebSocket Messages appear instantly, no page reload

AI Model    Qwen2.5-1.5B-Instruct via HuggingFace Transformers  Runs locally on CPU, 3GB RAM, good chat quality

Frontend    Vanilla JS + CSS    Zero dependencies, works offline, no CDN needed

Database    SQLite  Local file, zero setup, perfect for small group

Server  Daphne (ASGI)   Supports both HTTP and WebSocket

**Open-Source AI I Used**

Component                   What

LLM             Qwen2.5-1.5B-Instruct by Alibaba

Inference       HuggingFace Transformers

PyTorch PyTorch     (CPU build) 

Framework       Django

WebSocket       Django Channels 

**Privacy is not a feature, it's a fundamental right.**

If I used OpenAI's API or Google's Gemini API for this project, every message my friends send — their food preferences, their votes, their inside jokes, their travel plans — would be sent to a third-party server. That data would be logged, possibly used for training, and stored in someone else's database forever.

With open-weight models like Qwen, that doesn't happen.

My friends' data stays on my laptop. In a SQLite file that I control. I can delete it, back it up, or move it. Nobody else has access.

No API keys to leak. There are no API keys in the code. No .env file with secrets. No billing account to worry about.

No internet dependency. We used this at a cabin with no WiFi. I turned on mobile hotspot, everyone connected, and the AI still worked because Qwen runs locally.

No usage limits. No "you've exceeded your quota" errors at 3 AM when we're still planning. The model runs as long as my laptop is on.

Full customization. I can change the AI's personality, what it remembers, how it makes decisions. Try getting ChatGPT to always pick a restaurant based on your group's dietary restrictions. With open models, I just edit the system prompt.

Zero cost. Running Qwen2.5-1.5B on my laptop costs exactly $0. Forever. For unlimited messages, unlimited friends, unlimited decisions.

Open innovation made this possible because:

Qwen's Apache 2.0 license lets me use it commercially or personally without restrictions

HuggingFace Transformers provides the inference engine for free

Django's BSD license gives me a production-grade web framework for free

Django Channels adds real-time capabilities for free

A closed API would make this project either impossible (no offline mode), expensive (paying per message for 5 friends chatting all weekend), or privacy-violating (sending personal preferences to the cloud). Open models make it free, private, and offline.
