OpenAI Structured Output Demo
Mix.install(
[
:req,
:jason,
:kino
]
)
OpenAIClient
defmodule OpenAIClient do
@timeout 60_000
@base_url "https://api.openai.com/v1"
def call(model, messages, opts \\ []) do
body = %{
model: model,
messages: messages
}
|> Map.merge(Map.new(opts))
request(body)
|> Map.get(:body)
end
def get_response_message(body) do
get_in(body, ["choices", Access.at(0), "message", "content"])
end
def generate_webpage(prompt, model \\ "gpt-4o-2024-08-06") do
response_format =
%{
type: "json_schema",
json_schema: %{
name: "webpage_content",
schema: %{
type: "object",
properties: %{
html: %{
type: "string",
description: "The complete HTML content of the webpage"
},
css: %{
type: "string",
description: "The CSS styles for the webpage"
},
js: %{
type: "string",
description: "The JavaScript code for the webpage"
}
},
required: ["html", "css", "js"],
additionalProperties: false
},
strict: true
}
}
body = %{
model: model,
messages: [
%{
role: "system",
content: "You are a web developer. Create a single HTML file with embedded CSS and JavaScript based on the user's request. The HTML should be valid and self-contained."
},
%{
role: "user",
content: prompt
}
],
response_format: response_format
}
response = request(body)
case response do
%{status: 200, body: body} ->
body
|> get_response_message()
|> Jason.decode!()
_ ->
raise "API call failed: #{inspect(response.body)}"
end
end
defp request(body) do
api_key = System.fetch_env!("LB_OPENAI_API_KEY")
Req.post!(
"#{@base_url}/chat/completions",
json: body,
headers: [
{"Authorization", "Bearer #{api_key}"},
{"Content-Type", "application/json"}
],
receive_timeout: @timeout
)
end
end
Test API Call
# response = OpenAIClient.call("gpt-4o-mini", [
# %{role: "system", content: "You are a helpful assistant."},
# %{role: "user", content: "Write a haiku that explains the concept of recursion."}
# ])
# IO.puts(response |> OpenAIClient.get_response_message())
Create an input form using Kino
form = Kino.Control.form(
[
model: Kino.Input.text("Model", default: "gpt-4o-mini"),
system_message: Kino.Input.textarea("System Message", default: "You are a meticulous Elixir code reviewer who adheres to best practices and advocates for simplicity in coding."),
user_message: Kino.Input.textarea("User Message"),
temperature: Kino.Input.number("Temperature", default: 0.3)
],
submit: "Send Request"
)
Handle form submission
# # Simple example
# Kino.listen(form, fn %{data: data, origin: origin} ->
# IO.inspect(data)
# IO.inspect(origin)
# end)
# Complex example
# Create a frame to display the result
result_frame = Kino.Frame.new()
# Handle form submission
Kino.listen(form, fn %{data: data, origin: _origin} ->
messages = [
%{role: "system", content: data.system_message},
%{role: "user", content: data.user_message}
]
opts = [temperature: data.temperature]
response = OpenAIClient.call(data.model, messages, opts) |> IO.inspect()
# Pretty print the JSON response
formatted_response = OpenAIClient.get_response_message(response) # Jason.encode!(response, pretty: true)
Kino.Frame.render(result_frame, Kino.Markdown.new("```json\n#{formatted_response}\n```"))
end)
Kino.Layout.grid([
form,
result_frame
])
Demo to build HTML page
# Create an input form using Kino
input_form = Kino.Control.form(
[
prompt: Kino.Input.textarea("Describe the webpage you want to create")
],
submit: "Generate Webpage"
)
# Create frames to display the result
html_frame = Kino.Frame.new()
css_frame = Kino.Frame.new()
js_frame = Kino.Frame.new()
preview_frame = Kino.Frame.new()
# Handle form submission
Kino.listen(input_form, fn %{data: %{prompt: prompt}} ->
response = OpenAIClient.generate_webpage(prompt) |> IO.inspect()
html_content = response["html"]
css_content = response["css"]
js_content = response["js"]
# Combine HTML, CSS, and JS for preview
full_html = """
#{css_content}
#{html_content}
#{js_content}
"""
# Display the HTML content
Kino.Frame.render(html_frame,
Kino.Markdown.new("```html\n#{html_content}\n```")
)
# Display the CSS content
Kino.Frame.render(css_frame,
Kino.Markdown.new("```css\n#{css_content}\n```")
)
# Display the JS content
Kino.Frame.render(js_frame,
Kino.Markdown.new("```javascript\n#{js_content}\n```")
)
# Display the preview
Kino.Frame.render(preview_frame,
Kino.HTML.new(full_html)
)
end)
text = """
1. Choose a cool name for the cow, like "Gunner the Arsenal Cow."
2. Create a homepage with a catchy headline like "Book Your Picture with Gunner the Arsenal Cow Near the Emirates Stadium!"
3. Add a description of the experience, location details (near Emirates Stadium, London), and pricing information.
4. Include a calendar or booking system where users can select a date and time slot.
5. Set up an online payment system using Stripe or PayPal.
6. Add testimonials or a gallery of previous visitors with Gunner to build credibility.
"""
Kino.Layout.grid([
Kino.Markdown.new("## Input Form"),
input_form,
Kino.Markdown.new("## HTML"),
html_frame,
Kino.Markdown.new("## CSS"),
css_frame,
Kino.Markdown.new("## JavaScript"),
js_frame,
Kino.Markdown.new("## Preview"),
preview_frame
], columns: 1)