-
Notifications
You must be signed in to change notification settings - Fork 54
/
content_part.ex
112 lines (91 loc) · 3.08 KB
/
content_part.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
defmodule LangChain.Message.ContentPart do
@moduledoc """
Models a `ContentPart`. Some LLMs support combining text, images, and possibly
other content as part of a single user message. A `ContentPart` represents a
block, or part, of a message's content that is all of one type.
## Types
- `:text` - The message part is text.
- `:image_url` - The message part is a URL to an image.
- `:image` - The message part is image data that is base64 encoded text.
## Fields
- `:content` - Text content.
- `:options` - Options that may be specific to the LLM for a particular
message type. For example, Anthropic requires an image's `media_type` to be
provided by the caller. This can be provided using `media: "image/png"`.
"""
use Ecto.Schema
import Ecto.Changeset
require Logger
alias __MODULE__
alias LangChain.LangChainError
@primary_key false
embedded_schema do
field :type, Ecto.Enum, values: [:text, :image_url, :image], default: :text
field :content, :string
field :options, :any, virtual: true
end
@type t :: %ContentPart{}
@update_fields [:type, :content, :options]
@create_fields @update_fields
@required_fields [:type, :content]
@doc """
Build a new message and return an `:ok`/`:error` tuple with the result.
"""
@spec new(attrs :: map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
def new(attrs \\ %{}) do
%ContentPart{}
|> cast(attrs, @create_fields)
|> common_validations()
|> apply_action(:insert)
end
@doc """
Build a new message and return it or raise an error if invalid.
## Example
ContentPart.new!(%{type: :text, content: "Greetings!"})
ContentPart.new!(%{type: :image_url, content: "https://example.com/images/house.jpg"})
"""
@spec new!(attrs :: map()) :: t() | no_return()
def new!(attrs \\ %{}) do
case new(attrs) do
{:ok, message} ->
message
{:error, changeset} ->
raise LangChainError, changeset
end
end
@doc """
Create a new ContentPart that contains text. Raises an exception if not valid.
"""
@spec text!(String.t()) :: t() | no_return()
def text!(content) do
new!(%{type: :text, content: content})
end
@doc """
Create a new ContentPart that contains an image encoded as base64 data. Raises
an exception if not valid.
## Options
- `:media` - Provide the "media type" for the image. Examples: "image/jpeg",
"image/png", etc. ChatGPT does not require this but other LLMs may.
"""
@spec image!(String.t(), Keyword.t()) :: t() | no_return()
def image!(content, opts \\ []) do
new!(%{type: :image, content: content, options: opts})
end
@doc """
Create a new ContentPart that contains a URL to an image. Raises an exception if not valid.
"""
@spec image_url!(String.t()) :: t() | no_return()
def image_url!(content) do
new!(%{type: :image_url, content: content})
end
@doc false
def changeset(message, attrs) do
message
|> cast(attrs, @update_fields)
|> common_validations()
end
defp common_validations(changeset) do
changeset
|> validate_required(@required_fields)
end
end