{"id":25425052,"url":"https://github.com/dhonysilva/pulse","last_synced_at":"2026-04-30T10:07:25.488Z","repository":{"id":275363462,"uuid":"925851080","full_name":"dhonysilva/pulse","owner":"dhonysilva","description":"Learning how to streaming OpenAI in Elixir Phoenix ⚡️","archived":false,"fork":false,"pushed_at":"2025-02-15T20:28:24.000Z","size":369,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-14T02:14:58.577Z","etag":null,"topics":["elixir","openai","openai-api","phoenix-framework","phoenix-liveview"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dhonysilva.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-02-01T22:26:02.000Z","updated_at":"2025-02-15T20:28:27.000Z","dependencies_parsed_at":"2025-02-02T00:25:59.349Z","dependency_job_id":"c46d860b-c792-4d54-89ec-77d1b1a6d9b6","html_url":"https://github.com/dhonysilva/pulse","commit_stats":null,"previous_names":["dhonysilva/pulse"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dhonysilva/pulse","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhonysilva%2Fpulse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhonysilva%2Fpulse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhonysilva%2Fpulse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhonysilva%2Fpulse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dhonysilva","download_url":"https://codeload.github.com/dhonysilva/pulse/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhonysilva%2Fpulse/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32460880,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T22:27:22.272Z","status":"online","status_checked_at":"2026-04-30T02:00:05.929Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","openai","openai-api","phoenix-framework","phoenix-liveview"],"created_at":"2025-02-16T23:19:08.626Z","updated_at":"2026-04-30T10:07:25.429Z","avatar_url":"https://github.com/dhonysilva.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pulse\n\nApplication created while I was learning how to Streaming OpenAI in Elixir and Phoenix.\n\n\n## Learn more\nI walked through this tutorial's series by [Ben Reinhart](https://github.com/benjreinhart) following each step to build this project.\n\nHere is the sequence of tutorials:\n\n- [Part 01](https://benreinhart.com/blog/openai-streaming-elixir-phoenix/)\n- [Part 02](https://benreinhart.com/blog/openai-streaming-elixir-phoenix-part-2/)\n- [Part 03](https://benreinhart.com/blog/openai-streaming-elixir-phoenix-part-3/)\n\nIn order to develop this project, you will need to create an OpenAI account and obtain your API key.\n\nI'm storing my API key in a `.env` file in the root of the project. You can create your own `.env` file and add the following content:\n\n```bash\nOPENAI_API_KEY=\"your-openai-key\"\n```\n\nAdd the following dependencies to your `mix.exs` file:\n\n```elixir\n{:dotenv_parser, \"~\u003e 2.0\"},\n```\n\nAnd these instructions to your `runtime.exs`:\n\n```elixir\nif config_env() == :dev do\n  DotenvParser.load_file(\".env\")\nend\n```\n\n```elixir\nconfig :pulse, :openai, api_key: System.fetch_env!(\"OPENAI_API_KEY\")\n```\n\n\n### Setting up the project\nTo start this Phoenix server:\n\n  * Run `mix setup` to install and setup dependencies\n  * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`\n\nNow you can visit [`localhost:4000`](http://localhost:4000) from your browser.\n\n### Working with data\n\nDuring the developemnt, you can use the instructions below to interact wit the `chat_completion` function.\n\n```\n{:ok, %{body: response}} = Pulse.Openai.chat_completion(%{ model: \"gpt-3.5-turbo\", messages: [%{role: \"user\", content: \"Hello 3.5!\"}] })\n```\n\n```\n{:ok, %{body: response}} =\n\nPulse.Openai.chat_completion(\n  %{\n    model: \"gpt-3.5-turbo\",\n    messages: [%{role: \"user\", content: \"Hello 3.5!\"}]\n  },\n  \u0026IO.puts/1\n)\n```\n\n#### Playing with the `chat_completion/1` function.\n\nOpen your `iex` terminal.\n\n\nDefine the `messages` variable:\n\n```elixir\nmessages = [%{role: \"user\", content: \"O que é uma maçã em 5 palavras?\"}]\n```\n\nPassing the messages variable to the function calling:\n\n```elixir\n{:ok, %{body: response}} = Pulse.Openai.chat_completion(%{ model: \"gpt-3.5-turbo\", messages: messages })\n```\n\nYou can add more parameters as max_tokens, temperature, etc.\n\n```elixir\n{:ok, %{body: response}} = Pulse.Openai.chat_completion(%{ model: \"gpt-3.5-turbo\", max_tokens: 1000, temperature: 0, messages: messages })\n```\n\nHere is a simple way you can obtain the response `content` utilizing the `Map` functions:\n\n```elixir\ncontent = response |\u003e Map.get(\"choices\") |\u003e Enum.at(0) |\u003e Map.get(\"message\") |\u003e Map.get(\"content\")\n```\n\nAnd there's a better way to perform it, utilizing the pattern matching.\n\nTo obtain the `finish_reason`:\n\n```elixir\n%{\"choices\" =\u003e [%{\"finish_reason\" =\u003e finish_reason}]} = response\n```\n\nType `finish_reason` and you will see the value \"stop\".\n\nAnd to obtian the message `content`:\n\n```elixir\n%{\"choices\" =\u003e [%{\"message\" =\u003e %{ \"content\" =\u003e content }}]} = response\n```\n\nType `content` and you will see the value \"Fruta redonda e saborosa.\".\n\nAs you can see, pattern matching is a powerful tool in Elixir for parsing nested data structures. So, you can use it to extract the data you need. Get used to it.\n\n### Organizing the sequence of conversarion\n\n1. Define the messages Map\n\n```elixir\nmessages = [%{role: \"user\", content: \"O que é uma maçã em até 5 palavras?\"}]\n```\n\n2. Call the prompt with our initial messages Map\n\n```elixir\n{:ok, %{body: response}} = Pulse.Openai.chat_completion(%{ model: \"gpt-3.5-turbo\", max_tokens: 1000, temperature: 0, messages: messages })\n```\n\nIt will generate our response. Note the message Map returned:\n\n```json\n\"message\" =\u003e %{\n        \"content\" =\u003e \"Fruta redonda e saborosa.\",\n        \"refusal\" =\u003e nil,\n        \"role\" =\u003e \"assistant\"\n      }\n```\n\n3. Pattern matching the response to obtain the message node.\n\n```elixir\n%{\"choices\" =\u003e [%{\"message\" =\u003e message}]} = response\n```\n\n\n4. Add the message to the messages Map.\n\n```elixir\nmessages = messages ++ [message]\n```\n\nThis will be the result of the new messages Map\n\n```json\n[\n  %{role: \"user\", content: \"O que é uma maçã em até 5 palavras?\"},\n  %{\n    \"content\" =\u003e \"Fruta redonda e saborosa.\",\n    \"refusal\" =\u003e nil,\n    \"role\" =\u003e \"assistant\"\n  }\n]\n```\n\n\n5. Add the map for the next question to the messages Map.\n\n```elixir\nmessages = messages ++ [%{role: \"user\", content: \"E qual a sua cor?\"}]\n```\n\nThe new result of messages Map:\n\n```json\n[\n  %{role: \"user\", content: \"O que é uma maçã em até 5 palavras?\"},\n  %{\n    \"content\" =\u003e \"Fruta redonda e saborosa.\",\n    \"refusal\" =\u003e nil,\n    \"role\" =\u003e \"assistant\"\n  },\n  %{role: \"user\", content: \"E qual a sua cor?\"}\n]\n```\n\nAnd process the prompt again, now with the messages Map containing the first and the last message.\nProcessa novamente o prompt.\n\n```elixir\n{:ok, %{body: response}} = Pulse.Openai.chat_completion(%{ model: \"gpt-3.5-turbo\", max_tokens: 1000, temperature: 0, messages: messages })\n```\n\nNote the new content returned on the message Map.\n\n```json\n\"message\" =\u003e %{\n           \"content\" =\u003e \"Vermelha ou verde.\",\n           \"refusal\" =\u003e nil,\n           \"role\" =\u003e \"assistant\"\n         }\n```\n\n### Livebook\n\nFor an interactive example, you can utilize the [Livebook Getting Started - Consuming the Pulse functions](notebooks/getting_started.livemd). More instructions will be provided soon.\n\nStart the application with a fully qualified node name (the `--name` command-line option) with a cookie, and then conect the Livebook to it.\n\n```elixir\niex --name pulse-app@127.0.0.1 --cookie pulse-secret -S mix phx.server\n```\n\nSee below the configs with the _Remote execution_ smart cell.\n\n![The Livebook conection to the Phoenix Application node](priv/static/images/getting_started_livebook.png \"The Livebook navigation\")\n\n\n\n### The final project. How it looks like\n\nThe chat is available on chats router. You can visit [`localhost:4000/chats`](http://localhost:4000/chats) from your browser.\n\nThe text form to with your questions.\n\n![The text form to with your questions!](priv/static/images/question_form.png \"The text form\")\n\nThe response streaming from OpenAI.\n\n![The response streaming from OpenAI!](priv/static/images/response_openai_form.png \"The response streaming from OpenAI\")\n\n## Dependecies\n\nThis project relies on these dependencies:\n\n* Req ([link](https://hexdocs.pm/req/readme.html)), for HTTP requests\n* DotenvParser ([link](https://hexdocs.pm/dotenv_parser/DotenvParser.html)), for parsing `.env` files\n\n### Dealing with building artifacts problems\n\nSometimes there's some incompatibilies with the files on `_build` folder. In this case, proceed with one of the steps below.\n\nClear and recompile modules with:\n\n```bash\nmix compile --force\n```\n\nClear build artifacts and compile:\n\n```bash\nrm -rf _build\nmix deps.compile\nmix compile\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdhonysilva%2Fpulse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdhonysilva%2Fpulse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdhonysilva%2Fpulse/lists"}