chore: added more @spec
This commit is contained in:
@@ -17,7 +17,9 @@ defmodule BDS.AI.CatalogProvider do
|
||||
|
||||
def changeset(provider, attrs) do
|
||||
provider
|
||||
|> cast(attrs, [:id, :name, :env_keys, :package_ref, :api_url, :doc_url, :updated_at], empty_values: [nil])
|
||||
|> cast(attrs, [:id, :name, :env_keys, :package_ref, :api_url, :doc_url, :updated_at],
|
||||
empty_values: [nil]
|
||||
)
|
||||
|> validate_required([:id, :name, :updated_at])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,7 +25,9 @@ defmodule BDS.AI.ChatConversation do
|
||||
|
||||
def changeset(conversation, attrs) do
|
||||
conversation
|
||||
|> cast(attrs, [:id, :title, :model, :copilot_session_id, :created_at, :updated_at], empty_values: [nil])
|
||||
|> cast(attrs, [:id, :title, :model, :copilot_session_id, :created_at, :updated_at],
|
||||
empty_values: [nil]
|
||||
)
|
||||
|> validate_required([:id, :title, :created_at, :updated_at])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,18 +23,20 @@ defmodule BDS.AI.ChatMessage do
|
||||
|
||||
def changeset(message, attrs) do
|
||||
message
|
||||
|> cast(attrs, [
|
||||
:conversation_id,
|
||||
:role,
|
||||
:content,
|
||||
:tool_call_id,
|
||||
:tool_calls,
|
||||
:token_usage_input,
|
||||
:token_usage_output,
|
||||
:cache_read_tokens,
|
||||
:cache_write_tokens,
|
||||
:created_at
|
||||
], empty_values: [nil])
|
||||
|> cast(
|
||||
attrs,
|
||||
[
|
||||
:conversation_id,
|
||||
:role,
|
||||
:content,
|
||||
:tool_call_id,
|
||||
:tool_calls,
|
||||
:token_usage_input,
|
||||
:token_usage_output,
|
||||
:cache_read_tokens,
|
||||
:cache_write_tokens,
|
||||
:created_at
|
||||
], empty_values: [nil])
|
||||
|> validate_required([:conversation_id, :role, :created_at])
|
||||
|> assoc_constraint(:conversation)
|
||||
end
|
||||
|
||||
@@ -14,8 +14,10 @@ defmodule BDS.AI.ChatTools do
|
||||
project_id = project_id || active_project_id()
|
||||
|
||||
%{
|
||||
post_count: Repo.aggregate(from(post in Post, where: post.project_id == ^project_id), :count, :id),
|
||||
media_count: Repo.aggregate(from(media in Media, where: media.project_id == ^project_id), :count, :id),
|
||||
post_count:
|
||||
Repo.aggregate(from(post in Post, where: post.project_id == ^project_id), :count, :id),
|
||||
media_count:
|
||||
Repo.aggregate(from(media in Media, where: media.project_id == ^project_id), :count, :id),
|
||||
tag_count: Chat.count_distinct_string_list(Post, :tags, project_id),
|
||||
category_count: Chat.count_distinct_string_list(Post, :categories, project_id)
|
||||
}
|
||||
@@ -132,9 +134,28 @@ defmodule BDS.AI.ChatTools do
|
||||
project_tools =
|
||||
if is_binary(project_id) do
|
||||
[
|
||||
%{name: "blog_stats", spec: tool_spec("blog_stats", "Return aggregate blog statistics", %{"type" => "object", "properties" => %{}})},
|
||||
%{name: "list_posts", spec: tool_spec("list_posts", "List recent posts in the active project", limit_schema())},
|
||||
%{name: "list_media", spec: tool_spec("list_media", "List recent media items in the active project", limit_schema())}
|
||||
%{
|
||||
name: "blog_stats",
|
||||
spec:
|
||||
tool_spec("blog_stats", "Return aggregate blog statistics", %{
|
||||
"type" => "object",
|
||||
"properties" => %{}
|
||||
})
|
||||
},
|
||||
%{
|
||||
name: "list_posts",
|
||||
spec:
|
||||
tool_spec("list_posts", "List recent posts in the active project", limit_schema())
|
||||
},
|
||||
%{
|
||||
name: "list_media",
|
||||
spec:
|
||||
tool_spec(
|
||||
"list_media",
|
||||
"List recent media items in the active project",
|
||||
limit_schema()
|
||||
)
|
||||
}
|
||||
]
|
||||
else
|
||||
[]
|
||||
@@ -142,14 +163,62 @@ defmodule BDS.AI.ChatTools do
|
||||
|
||||
project_tools ++
|
||||
[
|
||||
%{name: "render_card", spec: tool_spec("render_card", "Return a structured card payload", render_card_schema())},
|
||||
%{name: "render_table", spec: tool_spec("render_table", "Return a structured table payload", render_table_schema())},
|
||||
%{name: "render_chart", spec: tool_spec("render_chart", "Return a structured chart payload", render_chart_schema())},
|
||||
%{name: "render_form", spec: tool_spec("render_form", "Return a structured form payload", render_form_schema())},
|
||||
%{name: "render_metric", spec: tool_spec("render_metric", "Return a structured metric payload", render_metric_schema())},
|
||||
%{name: "render_list", spec: tool_spec("render_list", "Return a structured list payload", render_list_schema())},
|
||||
%{name: "render_tabs", spec: tool_spec("render_tabs", "Return a structured tabs payload", render_tabs_schema())},
|
||||
%{name: "render_mindmap", spec: tool_spec("render_mindmap", "Return a structured mindmap payload", render_mindmap_schema())}
|
||||
%{
|
||||
name: "render_card",
|
||||
spec:
|
||||
tool_spec("render_card", "Return a structured card payload", render_card_schema())
|
||||
},
|
||||
%{
|
||||
name: "render_table",
|
||||
spec:
|
||||
tool_spec(
|
||||
"render_table",
|
||||
"Return a structured table payload",
|
||||
render_table_schema()
|
||||
)
|
||||
},
|
||||
%{
|
||||
name: "render_chart",
|
||||
spec:
|
||||
tool_spec(
|
||||
"render_chart",
|
||||
"Return a structured chart payload",
|
||||
render_chart_schema()
|
||||
)
|
||||
},
|
||||
%{
|
||||
name: "render_form",
|
||||
spec:
|
||||
tool_spec("render_form", "Return a structured form payload", render_form_schema())
|
||||
},
|
||||
%{
|
||||
name: "render_metric",
|
||||
spec:
|
||||
tool_spec(
|
||||
"render_metric",
|
||||
"Return a structured metric payload",
|
||||
render_metric_schema()
|
||||
)
|
||||
},
|
||||
%{
|
||||
name: "render_list",
|
||||
spec:
|
||||
tool_spec("render_list", "Return a structured list payload", render_list_schema())
|
||||
},
|
||||
%{
|
||||
name: "render_tabs",
|
||||
spec:
|
||||
tool_spec("render_tabs", "Return a structured tabs payload", render_tabs_schema())
|
||||
},
|
||||
%{
|
||||
name: "render_mindmap",
|
||||
spec:
|
||||
tool_spec(
|
||||
"render_mindmap",
|
||||
"Return a structured mindmap payload",
|
||||
render_mindmap_schema()
|
||||
)
|
||||
}
|
||||
]
|
||||
else
|
||||
[]
|
||||
|
||||
@@ -2,7 +2,11 @@ defmodule BDS.AI.HttpClient do
|
||||
@moduledoc false
|
||||
|
||||
def get(url, headers) when is_binary(url) and is_map(headers) do
|
||||
request = {String.to_charlist(url), Enum.map(headers, fn {key, value} -> {String.to_charlist(key), String.to_charlist(value)} end)}
|
||||
request =
|
||||
{String.to_charlist(url),
|
||||
Enum.map(headers, fn {key, value} ->
|
||||
{String.to_charlist(key), String.to_charlist(value)}
|
||||
end)}
|
||||
|
||||
:inets.start()
|
||||
:ssl.start()
|
||||
@@ -24,7 +28,10 @@ defmodule BDS.AI.HttpClient do
|
||||
def post(url, headers, body)
|
||||
when is_binary(url) and is_map(headers) and is_binary(body) do
|
||||
request =
|
||||
{String.to_charlist(url), Enum.map(headers, fn {key, value} -> {String.to_charlist(key), String.to_charlist(value)} end), ~c"application/json", body}
|
||||
{String.to_charlist(url),
|
||||
Enum.map(headers, fn {key, value} ->
|
||||
{String.to_charlist(key), String.to_charlist(value)}
|
||||
end), ~c"application/json", body}
|
||||
|
||||
:inets.start()
|
||||
:ssl.start()
|
||||
|
||||
@@ -34,31 +34,41 @@ defmodule BDS.AI.Model do
|
||||
|
||||
def changeset(model, attrs) do
|
||||
model
|
||||
|> cast(attrs, [
|
||||
|> cast(
|
||||
attrs,
|
||||
[
|
||||
:provider,
|
||||
:model_id,
|
||||
:name,
|
||||
:family,
|
||||
:supports_attachment,
|
||||
:supports_reasoning,
|
||||
:supports_tool_calls,
|
||||
:supports_structured_output,
|
||||
:supports_temperature,
|
||||
:knowledge,
|
||||
:release_date,
|
||||
:last_updated_date,
|
||||
:open_weights,
|
||||
:input_price,
|
||||
:output_price,
|
||||
:cache_read_price,
|
||||
:cache_write_price,
|
||||
:context_window,
|
||||
:max_input_tokens,
|
||||
:max_output_tokens,
|
||||
:interleaved,
|
||||
:status,
|
||||
:updated_at
|
||||
], empty_values: [nil])
|
||||
|> validate_required([
|
||||
:provider,
|
||||
:model_id,
|
||||
:name,
|
||||
:family,
|
||||
:supports_attachment,
|
||||
:supports_reasoning,
|
||||
:supports_tool_calls,
|
||||
:supports_structured_output,
|
||||
:supports_temperature,
|
||||
:knowledge,
|
||||
:release_date,
|
||||
:last_updated_date,
|
||||
:open_weights,
|
||||
:input_price,
|
||||
:output_price,
|
||||
:cache_read_price,
|
||||
:cache_write_price,
|
||||
:context_window,
|
||||
:max_input_tokens,
|
||||
:max_output_tokens,
|
||||
:interleaved,
|
||||
:status,
|
||||
:updated_at
|
||||
], empty_values: [nil])
|
||||
|> validate_required([:provider, :model_id, :name, :context_window, :max_input_tokens, :max_output_tokens, :updated_at])
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,12 +30,13 @@ defmodule BDS.AI.OpenAICompatibleRuntime do
|
||||
}
|
||||
|> maybe_put_auth(endpoint.api_key)
|
||||
|
||||
payload = %{
|
||||
"model" => request.model,
|
||||
"messages" => request.messages,
|
||||
"max_tokens" => request.max_output_tokens
|
||||
}
|
||||
|> maybe_put_tools(request.tools)
|
||||
payload =
|
||||
%{
|
||||
"model" => request.model,
|
||||
"messages" => request.messages,
|
||||
"max_tokens" => request.max_output_tokens
|
||||
}
|
||||
|> maybe_put_tools(request.tools)
|
||||
|
||||
with {:ok, response} <- HttpClient.post(url, headers, Jason.encode!(payload)),
|
||||
200 <- response.status do
|
||||
@@ -55,7 +56,9 @@ defmodule BDS.AI.OpenAICompatibleRuntime do
|
||||
|
||||
json =
|
||||
case content do
|
||||
nil -> nil
|
||||
nil ->
|
||||
nil
|
||||
|
||||
value when is_binary(value) ->
|
||||
case Jason.decode(value) do
|
||||
{:ok, decoded} when is_map(decoded) -> decoded
|
||||
@@ -77,10 +80,17 @@ defmodule BDS.AI.OpenAICompatibleRuntime do
|
||||
|
||||
defp models_url(url) do
|
||||
cond do
|
||||
String.ends_with?(url, "/chat/completions") -> String.replace_suffix(url, "/chat/completions", "/models")
|
||||
String.ends_with?(url, "/models") -> url
|
||||
String.ends_with?(url, "/") -> url <> "models"
|
||||
true -> url <> "/models"
|
||||
String.ends_with?(url, "/chat/completions") ->
|
||||
String.replace_suffix(url, "/chat/completions", "/models")
|
||||
|
||||
String.ends_with?(url, "/models") ->
|
||||
url
|
||||
|
||||
String.ends_with?(url, "/") ->
|
||||
url <> "models"
|
||||
|
||||
true ->
|
||||
url <> "/models"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -114,7 +124,9 @@ defmodule BDS.AI.OpenAICompatibleRuntime do
|
||||
|
||||
defp maybe_put_auth(headers, nil), do: headers
|
||||
defp maybe_put_auth(headers, ""), do: headers
|
||||
defp maybe_put_auth(headers, api_key), do: Map.put(headers, "authorization", "Bearer #{api_key}")
|
||||
|
||||
defp maybe_put_auth(headers, api_key),
|
||||
do: Map.put(headers, "authorization", "Bearer #{api_key}")
|
||||
|
||||
defp maybe_put_tools(payload, []), do: payload
|
||||
defp maybe_put_tools(payload, nil), do: payload
|
||||
|
||||
@@ -65,7 +65,9 @@ defmodule BDS.AI.Runtime do
|
||||
end
|
||||
|
||||
defp resolve_model_for_operation(:analyze_image, :airplane, endpoint, extra) do
|
||||
{:ok, Keyword.get(extra, :model) || model_preference_value(:airplane_image_analysis) || endpoint.model}
|
||||
{:ok,
|
||||
Keyword.get(extra, :model) || model_preference_value(:airplane_image_analysis) ||
|
||||
endpoint.model}
|
||||
end
|
||||
|
||||
defp resolve_model_for_operation(:analyze_image, :online, endpoint, extra) do
|
||||
@@ -83,7 +85,8 @@ defmodule BDS.AI.Runtime do
|
||||
defp fetch_endpoint_for_mode(mode, secret_backend) do
|
||||
with {:ok, endpoint} <- AI.get_endpoint(mode, secret_backend: secret_backend) do
|
||||
case endpoint do
|
||||
%{url: url, model: model} = loaded when is_binary(url) and url != "" and is_binary(model) and model != "" ->
|
||||
%{url: url, model: model} = loaded
|
||||
when is_binary(url) and url != "" and is_binary(model) and model != "" ->
|
||||
if mode == :online and blank?(loaded.api_key) do
|
||||
{:error, %{kind: :endpoint_not_configured, endpoint: mode}}
|
||||
else
|
||||
|
||||
@@ -17,7 +17,15 @@ defmodule BDS.AI.SecretBackend do
|
||||
with {:ok, binary} <- Base.decode64(encoded),
|
||||
<<iv::binary-size(12), tag::binary-size(16), ciphertext::binary>> <- binary,
|
||||
plaintext when is_binary(plaintext) <-
|
||||
:crypto.crypto_one_time_aead(:aes_256_gcm, secret_key(), iv, ciphertext, @aad, tag, false) do
|
||||
:crypto.crypto_one_time_aead(
|
||||
:aes_256_gcm,
|
||||
secret_key(),
|
||||
iv,
|
||||
ciphertext,
|
||||
@aad,
|
||||
tag,
|
||||
false
|
||||
) do
|
||||
{:ok, plaintext}
|
||||
else
|
||||
_other -> {:error, :invalid_ciphertext}
|
||||
|
||||
@@ -21,8 +21,8 @@ defmodule BDS.AI.SettingsStore do
|
||||
def put_setting(key, value) when is_binary(key) and is_binary(value) do
|
||||
now = Persistence.now_ms()
|
||||
|
||||
(%Setting{}
|
||||
|> Setting.changeset(%{key: key, value: value, updated_at: now}))
|
||||
%Setting{}
|
||||
|> Setting.changeset(%{key: key, value: value, updated_at: now})
|
||||
|> Repo.insert(
|
||||
on_conflict: [set: [value: value, updated_at: now]],
|
||||
conflict_target: [:key]
|
||||
|
||||
Reference in New Issue
Block a user