Hmm … In the context of a tool that is known from returning fake information? 
So how do you ever image verifying LLM output? 
I’m not saying don’t use LLM at all, but consider it only as a tool and not as a human that would do everything for you perfectly. Please keep in mind that even people makes mistakes - it’s like a part of our nature. So do you really imagine that you can generate something perfectly using a tool that is definitely not perfect and you would not need to validate it (think about scale)? 
The more code you write the more bugs it have. That’s one of many dev rules you most probably are familiar with already. Now think that LLM that sometimes (in my experience “often” may be a better word) generates a fake information would be asked to convert a really big project. You may even need convert your app part by part and verify each line which in my opinion does not makes sense. Statistically at this scale you have almost 100% chance that something would be completely wrong. 
For me it’s not a problem. I did not used Ash, but it’s just a matter of learning another package and recently I become very good in metaprogramming and I have much bigger experience in rewriting old code than I would ever wish. 
If you are scared I can do it for you. Of course it would not be for free and it would take longer than using LLM, but I can give you an application that have full specification (even for private functions), documentation and 100% test coverage. 
What’s more important you would not need to verify each line of the code and spend any time on this project. That’s a perfect definition of the job i.e. you pay for someone skills and time. For sure it’s not what you have asked, but as said before it’s very hard to rely on LLM without verifying it’s output. 
In case you think that I’m just too sceptical about LLM … Fine, try me! Try something much simpler that would not lose much of your time … Ask your favourite LLM to generate an email validation using nimble_parsec that covers all RFCs related to email format. In my experience LLM often tried to “guess” macro name. It didn’t think about nimble_parsec’s documentation, but used a non existing macros that would fit it’s needs or it made a similar mistakes. 
After few tries and asking gently to use a real macros I decided to remove the discussion as for me it was obvious that I was only wasting my time which I could spend on real code. Try yourself and then think how much Ash rely on macros … 
I will give here a hand and show how it looks from my perspective … Here is for example a code from GPT-4.1 response generated by Github Copilot:
defmodule EmailCombinator do
import NimbleParsec
# RFC 5322 defines the grammar, but it's very complex.
# We'll build a practical implementation that closely matches RFC 5322,
# including support for quoted strings and domain literals.
# Allowed characters in atoms (unquoted local-part segments)
# See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.3
atom_char =
ascii_char([
?a..?z,
?A..?Z,
?0..?9,
?!,
?#,
?$,
?%,
?&,
?',
?*,
?+,
?-,
?/,
?=,
??,
?^,
?_,
?`,
?{,
?|,
?},
?~
])
dot_atom_text =
atom_char
|> repeat()
|> ignore()
|> repeat(
string(".")
|> concat(repeat(atom_char))
|> ignore()
)
dot_atom =
dot_atom_text
|> label("dot-atom")
# Quoted string for local part
# See: https://datatracker.ietf.org/doc/html/rfc5322#section-3.2.4
# Allows backslash-escaped characters and quoted-pairs inside double quotes
qtext =
ascii_char([0..127])
|> lookahead_not(string("\"") <|> string("\\") <|> ascii_char([0..31, 127]))
quoted_pair = string("\\") |> concat(ascii_char([0..127]))
quoted_string =
ignore(string("\""))
|> repeat(choice([qtext, quoted_pair]))
|> ignore(string("\""))
|> label("quoted-string")
local_part =
choice([dot_atom, quoted_string])
|> label("local-part")
# Domain: dot-atom or domain-literal
# Domain literal: [ ... ], can contain any ASCII except [\, ] or CRLF
dtext =
ascii_char([33..90, 94..126])
domain_literal =
ignore(string("["))
|> repeat(choice([dtext, quoted_pair]))
|> ignore(string("]"))
|> label("domain-literal")
domain =
choice([dot_atom, domain_literal])
|> label("domain")
# Full addr-spec parser
email_parser =
local_part
|> ignore(string("@"))
|> concat(domain)
|> eos()
defparsec(:parse, email_parser)
@doc """
Validates an email address according to RFC 5322 and related RFCs.
## Examples
iex> EmailCombinator.valid?("test@example.com")
true
iex> EmailCombinator.valid?("\"foo bar\"@example.org")
true
iex> EmailCombinator.valid?("foo@[192.168.1.1]")
true
iex> EmailCombinator.valid?("invalid@")
false
"""
def valid?(email) when is_binary(email) do
case parse(email) do
{:ok, _, "", _, _, _} -> true
_ -> false
end
end
end
I have only added a Mix.install/1 call above this module and wrote such code to example.exs. Just look at the compilation:
warning: <|> is deprecated. Use another pipe-like operator
│
57 │ |> lookahead_not(string("\"") <|> string("\\") <|> ascii_char([0..31, 127]))
│ ~
│
└─ example.exs:57:35
warning: <|> is deprecated. Use another pipe-like operator
│
57 │ |> lookahead_not(string("\"") <|> string("\\") <|> ascii_char([0..31, 127]))
│ ~
│
└─ example.exs:57:52
error: undefined function <|>/2 (there is no such import)
│
57 │ |> lookahead_not(string("\"") <|> string("\\") <|> ascii_char([0..31, 127]))
│ ^
│
└─ example.exs:57:52: EmailCombinator (module)
** (CompileError) example.exs: cannot compile module EmailCombinator (errors have been logged)
(elixir 1.19.0-rc.0) expanding macro: Kernel.|>/2
example.exs:57: EmailCombinator (module)
The prompt:
You are a senior Elixir developer. Please generate an email validation function in Elixir language using nimble_parsec hex package. The function needs to match all RFCs that describes email format/validation. Please do not use non existing macros and provide a code that would be able to compile without any issues.
The rest of the response:
Notes:
- This implementation matches most addresses allowed by RFC 5322, including quoted strings for the local part and domain literals (e.g.,
[192.168.1.1]).
- It does not attempt to validate comments, obsolete elements, or some pathological edge cases allowed by the RFCs, as those are rarely used and would complicate the parser considerably.
- It is practical, robust, and will compile/run with
nimble_parsec as dependency.
- To use, add
{:nimble_parsec, "~> 1.0"} to your mix.exs dependencies.
Usage Example:
EmailCombinator.valid?("foo@[127.0.0.1]") # true
EmailCombinator.valid?("bad@") # false
So if you want a code that does not match prompt (only single RFC covered instead of all), you think that a senior Elixir developer would use non existing operator <|> and your old code does not cover a pathological edge cases / rarely used cases (who asked for it?) then feel free to try it on a larger project … 
With all my experience I can’t tell how to prevent something from generating that was not even mentioned in the prompt. Maybe it’s just because my English. Please prove I’m wrong and show that we can write prompts to avoid such problems. It would be very helpful for me as well! 
I’m really sorry to say that as I was also trying to generate some code from various LLM, but at least in my opinion it always ends like that i.e. the more I ask LLM for, the bigger chance is for a fail like that. Maybe I’m wrong or not smart enough or something, but I really recommend to get a help from a real person. Anyway, whatever you decide, good luck. 