• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / backend / e47b823d3b853d3fe9abe51358a2c5f3a1892ca7-PR-1151

17 Feb 2025 06:37AM UTC coverage: 93.613% (+0.008%) from 93.605%
e47b823d3b853d3fe9abe51358a2c5f3a1892ca7-PR-1151

Pull #1151

github

web-flow
Merge branch 'master' into payload_size_limit
Pull Request #1151: Add payload size limit

5 of 7 new or added lines in 2 files covered. (71.43%)

3107 of 3319 relevant lines covered (93.61%)

1063.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.65
/lib/cadet_web/controllers/chat_controller.ex
1
defmodule CadetWeb.ChatController do
2
  @moduledoc """
3
  Handles the chatbot conversation API endpoints.
4
  """
5
  use CadetWeb, :controller
6
  use PhoenixSwagger
7

8
  alias Cadet.Chatbot.{Conversation, LlmConversations}
9
  @max_content_size 1000
10

11
  def init_chat(conn, %{"section" => section, "initialContext" => initialContext}) do
12
    user = conn.assigns.current_user
1✔
13

14
    if is_nil(section) do
1✔
15
      send_resp(conn, :bad_request, "Missing course section")
1✔
16
    else
17
      case LlmConversations.create_conversation(user.id, section, initialContext) do
×
18
        {:ok, conversation} ->
19
          conn
20
          |> put_status(:created)
21
          |> render(
×
22
            "conversation_init.json",
23
            %{
24
              conversation_id: conversation.id,
×
NEW
25
              last_message: conversation.messages |> List.last(),
×
26
              max_content_size: @max_content_size
27
            }
28
          )
29

30
        {:error, error_message} ->
31
          send_resp(conn, :unprocessable_entity, error_message)
×
32
      end
33
    end
34
  end
35

36
  swagger_path :chat do
1✔
37
    put("/chat")
38

39
    summary("A wrapper for client that send queries to LLMs")
40

41
    security([%{JWT: []}])
42

43
    consumes("application/json")
44

45
    parameters do
46
      message(
47
        :body,
48
        :list,
49
        "Conversation history. Need to be an non empty list of format {role: string, content:string}. For more details, refer to https://platform.openai.com/docs/api-reference/chat/create"
50
      )
51
    end
52

53
    response(200, "OK")
54
    response(400, "Missing or invalid parameter(s)")
55
    response(401, "Unauthorized")
56
    response(422, "Message exceeds the maximum allowed length")
57
    response(500, "When OpenAI API returns an error")
58
  end
59

60
  def chat(conn, %{"conversationId" => conversation_id, "message" => user_message}) do
61
    user = conn.assigns.current_user
5✔
62

63
    with true <- String.length(user_message) <= @max_content_size || {:error, :message_too_long},
5✔
64
         {:ok, conversation} <-
4✔
65
           LlmConversations.get_conversation_for_user(user.id, conversation_id),
4✔
66
         {:ok, updated_conversation} <-
1✔
67
           LlmConversations.add_message(conversation, "user", user_message),
68
         payload <- generate_payload(updated_conversation) do
1✔
69
      case OpenAI.chat_completion(model: "gpt-4", messages: payload) do
1✔
70
        {:ok, result_map} ->
71
          choices = Map.get(result_map, :choices, [])
1✔
72
          bot_message = Enum.at(choices, 0)["message"]["content"]
1✔
73

74
          case LlmConversations.add_message(updated_conversation, "assistant", bot_message) do
1✔
75
            {:ok, _} ->
76
              render(conn, "conversation.json", %{
1✔
77
                conversation_id: conversation_id,
78
                response: bot_message
79
              })
80

81
            {:error, error_message} ->
82
              send_resp(conn, 500, error_message)
×
83
          end
84

85
        {:error, reason} ->
86
          error_message = reason["error"]["message"]
×
87
          IO.puts("Error message from openAI response: #{error_message}")
×
88
          LlmConversations.add_error_message(updated_conversation)
×
89
          send_resp(conn, 500, error_message)
×
90
      end
91
    else
92
      {:error, :message_too_long} ->
93
        send_resp(
1✔
94
          conn,
95
          :unprocessable_entity,
96
          "Message exceeds the maximum allowed length of #{@max_content_size}"
1✔
97
        )
98

99
      {:error, {:not_found, error_message}} ->
100
        send_resp(conn, :not_found, error_message)
3✔
101

102
      {:error, error_message} ->
103
        send_resp(conn, 500, error_message)
×
104
    end
105
  end
106

107
  @context_size 20
108

109
  @spec generate_payload(Conversation.t()) :: list(map())
110
  defp generate_payload(conversation) do
111
    # Only get the last 20 messages into the context
112
    messages_payload =
1✔
113
      conversation.messages
1✔
114
      |> Enum.reverse()
115
      |> Enum.take(@context_size)
116
      |> Enum.map(&Map.take(&1, [:role, :content, "role", "content"]))
2✔
117
      |> Enum.reverse()
118

119
    conversation.prepend_context ++ messages_payload
1✔
120
  end
121

122
  def max_content_length, do: @max_content_size
2✔
123
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc