{"id":499,"date":"2025-03-26T15:55:20","date_gmt":"2025-03-26T15:55:20","guid":{"rendered":"https:\/\/liverickson.com\/blog\/?p=499"},"modified":"2025-03-26T15:55:20","modified_gmt":"2025-03-26T15:55:20","slug":"building-myself-a-english-to-english-translator-in-slack","status":"publish","type":"post","link":"https:\/\/liverickson.com\/blog\/?p=499","title":{"rendered":"Building Myself a English-to-English Translator in Slack"},"content":{"rendered":"\n<p>English is English, right? <\/p>\n\n\n\n<p>I&#8217;m not sure if it&#8217;s just me, but this feels increasingly less true as AI threatens to take over the way that we think and communicate. I&#8217;ve been working in product for over a decade, and I should be used to the role of connecting the business world with the technical one&#8230; except it feels like now it&#8217;s harder than ever to use common language. <\/p>\n\n\n\n<p>I&#8217;ve written about subjectivity in reality before, and that&#8217;s something that is becoming more obvious and apparent to me on a day to day basis. As someone who is neurodivergent, I&#8217;m actually relatively used to the gap of how I see the world and how others do, but it seems like it&#8217;s been expanded to an entirely new scale lately with AI. <\/p>\n\n\n\n<p>One of the biggest challenges in this space is seeing this huge gap in context across individual experiences, and how it&#8217;s amplifying workplace dysfunction and miscommunications. Companies lose <a href=\"https:\/\/itnation.lu\/news\/siemens-not-addressing-inefficient-communication-issues-can-cause-huge-losses\/#:~:text=Communications%20barriers%20and%20latencies%20can,sponsored%20global%20study\">hundreds of thousands of dollars per year<\/a> on unresolved communication and coordination issues, waste <a href=\"https:\/\/www.panopto.com\/blog\/how-much-time-is-lost-to-knowledge-sharing-inefficiencies-at-work\/#:~:text=1,Knowledge\">19 hours per week on knowledge-sharing inefficiencies<\/a>, and disengaged employees <a href=\"http:\/\/[2] https:\/\/www.worklytics.co\/blog\/what-is-employee-disengagement\">cost employers more than $154 billion<\/a> annually. It is not only bad for business, it&#8217;s bad for individuals. A 2023 study by Asana found that 88% of product managers feel stressed often or all of the time, and the increasing pressure for PMs to become &#8216;super ICs&#8217; doesn&#8217;t solve the loneliness challenges that young American adults are facing. During a time of significant economic uncertainty and upheaval, organizational pressures are mounting. <\/p>\n\n\n\n<p>So what is one to do? <\/p>\n\n\n\n<p>Write an English-to-English translator. <\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">import os<br>import requests<br>import logging<br>from slack_bolt import App<br>from slack_bolt.adapter.flask import SlackRequestHandler<br>from flask import Flask, request, make_response<br><br><br>logging.basicConfig(level=logging.DEBUG)<br><br>def load_prompt_template():<br>    try:<br>        with open(\"prompt_template.txt\", \"r\") as file:<br>            return file.read().strip()<br>    except Exception as e:<br>        logging.error(\"Error loading prompt template: %s\", e)<br>        # Fallback default prompt if file isn't available.<br>        return \"Read the provided context and make suggestions on how to reframe it for a professional environment. Context:\"<br><br>prompt_template = load_prompt_template()<br><br>app = App(<br>    token='YOUR_APP_TOKEN_HERE',<br>    signing_secret='YOUR_SIGNING_SECRET_HERE'<br>)<br><br>flask_app = Flask(__name__)<br>handler = SlackRequestHandler(app)<br><br>def get_ollama_response(user_input):<br>    url = \"http:\/\/localhost:11434\/api\/generate\"<br>    full_prompt = f\"{prompt_template} {user_input}\"<br>    payload = {<br>        \"model\": \"qwen2.5\",<br>        \"prompt\": full_prompt,<br>        \"stream\": False  # Ensure a complete, non-streamed response.<br>    }<br>    try:<br>        logging.debug(\"Sending request to %s with payload %s\", url, payload)<br>        response = requests.post(url, json=payload)<br>        logging.debug(\"Response status code: %s\", response.status_code)<br>        logging.debug(\"Response text: %s\", response.text)<br>        response.raise_for_status()<br>        json_data = response.json()<br>        return json_data.get(\"response\", \"No response from Ollama.\")<br>    except requests.RequestException as e:<br>        logging.error(\"Error communicating with Ollama: %s\", e)<br>        return f\"Error communicating with Ollama: {e}\"<br><br>@app.event(\"app_mention\")<br>def handle_app_mention(body, client, logger):<br>    event = body.get(\"event\", {})<br>    user = event.get(\"user\")<br>    channel = event.get(\"channel\")<br>    text = event.get(\"text\", \"\")<br>    <br>    response_text = get_ollama_response(text)<br>    <br>    try:<br>        client.chat_postEphemeral(<br>            channel=channel,<br>            user=user,<br>            text=response_text<br>        )<br>    except Exception as e:<br>        logger.error(\"Error sending ephemeral message: %s\", e)<br><br>@flask_app.route(\"\/slack\/events\", methods=[\"POST\"])<br>def slack_events():<br>    data = request.get_json(silent=True)<br>    logging.debug(\"Received Slack event: %s\", data)<br>    <br>    if data and data.get(\"type\") == \"url_verification\":<br>        challenge = data.get(\"challenge\")<br>        logging.debug(\"Responding to URL verification with challenge: %s\", challenge)<br>        return make_response(challenge, 200, {\"Content-Type\": \"text\/plain\"})<br>    <br>    return handler.handle(request)<br><br>if __name__ == \"__main__\":<br>    port = int(os.environ.get(\"PORT\", 3000))<br>    flask_app.run(host=\"0.0.0.0\", port=port)<br><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">What it does<\/h2>\n\n\n\n<p>The python file above creates a basic Flask App that handles app routes from Slack messages and queries a local Ollama instance. The app is designed to work in both DM form and in group channels when it is @mentioned, which theoretically could help people in a team collaborate on framing a particular message. Right now, I&#8217;m routing it through <a href=\"https:\/\/dashboard.ngrok.com\/endpoints\">ngrok<\/a> <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What it doesn&#8217;t do<\/h2>\n\n\n\n<p>Most of what I want it to, at this stage, but it&#8217;s hard to test a multi-user workflow alone in a single-person Slack workspace. Now that I&#8217;ve gotten the bones of the app in place, I&#8217;m going to be playing around with some additional capabilities &#8211; like being able to send a few recent messages from a channel as additional context for the LLM, or being able to hook this up to a RAG embedding system so that it has better context for my projects to construct more accurate responses. I also am (again) finding myself ready to re-invest some more time into my local LLM fine-tuning project, because I want the responses to read more like something that I would actually say.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Can this actually help?<\/h2>\n\n\n\n<p>I don&#8217;t know yet. I just built it this morning. So far, it seems promising, though like any AI application that relies on LLMs, it will be a lot better once I:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Spend time working on refining the prompt. I&#8217;ve recently been experimenting with asking ChatGPT to generate prompts for me, and it definitely (&#8220;Certainly!&#8221;) helped with reducing some of the &#8220;AI tells&#8221;, but there&#8217;s a long way to go to make it sound less mechanical. <br><\/li>\n\n\n\n<li>Test different models and prompt combinations. I&#8217;m using <a href=\"http:\/\/transformerlab.ai\">Transformer Lab<\/a> to run different experiments, and right now, I default to using Qwen2.5 because it works quickly on my laptop and has reasonable enough answers for the time being, but I wouldn&#8217;t be surprised if a different model that was fine-tuned on more casual conversations had better results. <br><\/li>\n\n\n\n<li>Fine-tune whatever model is most promising on my own Q&amp;A dataset, which I&#8217;m slowly working through. <\/li>\n<\/ol>\n\n\n\n<p>That all said, I&#8217;ve already started using it to help me craft responses and messages to send to my team in Slack. It&#8217;s a faster workflow than swapping over to ChatGPT, though it&#8217;s not as feature-rich as <a href=\"https:\/\/goblin.tools\/Formalizer\">Goblin.Tools<\/a>, which I was introduced to after talking through some of the challenges that I&#8217;ve been facing with some co-workers. <\/p>\n\n\n\n<p>All said, I&#8217;m pleased with how quickly I was able to get this up and running, and this feels like a meaningful first step in figuring out how to make a useful, context-aware professional communication coach. More to come!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m not sure if it&#8217;s just me, but this feels increasingly less true as AI threatens to take over the way that we think and communicate. What is one to do? Write an English-to-English translator. <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":0,"activitypub_interaction_policy_quote":"","activitypub_status":"federate","footnotes":""},"categories":[2,5,14],"tags":[],"class_list":["post-499","post","type-post","status-publish","format-standard","hentry","category-communication","category-machine-learning","category-neurodivergence"],"_links":{"self":[{"href":"https:\/\/liverickson.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/499","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/liverickson.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/liverickson.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/liverickson.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/liverickson.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=499"}],"version-history":[{"count":2,"href":"https:\/\/liverickson.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/499\/revisions"}],"predecessor-version":[{"id":501,"href":"https:\/\/liverickson.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/499\/revisions\/501"}],"wp:attachment":[{"href":"https:\/\/liverickson.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=499"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/liverickson.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=499"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/liverickson.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=499"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}