Phoenix 的生势并连结上 ModelRiver

这全用拉和基于以在挂于打着那 Elixir 此尊教宗门类旗下造立作大建筑的 Phoenix 所造那类生生不息在即现眼当下里打阵实能跑得打起飞快跟真见血肉带实感的 AI 面应用的此建台下。能通拉包含唤带其长有这唤 LiveView 的长线下拉引水长留连珠炮瀑字神图、连根上跟扎立立定在门道专线通讯口这走法下的 (channel-based) 图打闲拉小话大作作台、还要并也括容和管辖起那类以收长吊飞线暗口在后台 (webhook-driven) 所拨所促引向发动的这种暗起后台收活的排大水收差法管线大流槽子大排线等。

纵览俯探个探究竟的大统眼光底去起底 (Overview)

你得把这话给说透听明也就是 Phoenix 早已在这挂名牌叫做 Elixir 的整个派天下中称霸立顶在作最冠带最极红打头的建大 web 应站起这头号高楼主框架总教主其地位尊,这人生打根里天生这基因是早是定被框着为专去承专去接去打就那讲极实时见真分争见秒而且还讲极这挂起无常无感有着生打硬熬容这死防死坠神断断线且跌宕保不坠机的这极扛强死大不跌耐绝命命大不绝法门(fault-tolerant)容坠应用网机生的大面造面机所来的。当打把它给请拉牵连再把咱们这也挂上了抗跌摔的这种带大备线胎叫 ModelRiver 跟它这两相互同打底相挂合,你这一门就可以全端开拔生去拉起造生那种自带极厚重有着满挂打气 AI 活这神力的活那叫 LiveView 版界面小前屏(UIs)、还有连上并串入在去在去向那连在这通讯多门多专脉频专线之大作里的话局拉流台,另外这当然还不包那能连收打拉引的潜入下边后台作这连收拉飞线走(webhook-driven)去做大苦在后台下作大消化处理洗包管件大流程等等的法事作这做局排。

到你手里的实落筹码和进账项下入瓮之大头里底实到底都有这好底:

  • 出带这全拉通注上赋神被带有大 AI 力力包且有长拉这即跑带水在即实马上现拉流的长出能线能的走那这专制门下属那 LiveView 的那群面子表套件子
  • 以挂线在通门频道这条路拉走线出声拉扯聊天出单并带生能由和自这背主自带所底有从由这 ModelRiver 顶给自带且带出母所供内挂之给发下的这种对叫做以 WebSocket 为生支撑作护底这法底的大力后援支保台
  • 设给这设并放拉专门以候命收取潜暗后传大丢勾索(webhook receivers)飞底线口的小接候接差小暗哨使之为伺以便来用和收走作打给 AI 走后发和在去拉出办异行大时异步(async)这活口给来收用
  • 把拉跟建上和有这一手绝的去大立抗跌保不会防不轻易见坠连保不断绝去大坠这种挂打 AI 向这种坚挺排差水槽列能就全是因为去引并仗用这全凭并全仗全用得生那出自其于生养它这总教大当老门名下 Elixir 自身自生来自身骨里大叫名为并号作所唤叫 OTP 监视这打护卫长守挂看命的这颗绝防卫死不跌命的监督之树(supervision trees)大招神护

疾响战雷点大起步快门跟这进前冲着拉 (Quick start)

把随身要拉扯上阵走着挂所依之这各宗家什行军包裹依赖跟料包齐打跟凑齐端上来添

ELIXIR
1# mix.exs
2defp deps do
3 [
4 {:phoenix, "~> 1.7"},
5 {:req, "~> 0.5"},
6 {:jason, "~> 1.4"},
7 ]
8end

给它把家门另造也给设建一个自独立包包拉来就来独门并单领差的这包下作 AI 的出巡门客连招收模代包 (Create an AI client module)

ELIXIR
1defmodule MyApp.AI do
2 @base_url "https://api.modelriver.com/v1"
3 @api_key System.get_env("MODELRIVER_API_KEY")
4 
5 def chat(workflow, messages, opts \\ []) do
6 body = %{
7 workflow: workflow,
8 messages: messages
9 }
10 |> Map.merge(Map.new(opts))
11 
12 Req.post!("#{@base_url}/ai",
13 json: body,
14 headers: [{"authorization", "Bearer #{@api_key}"}]
15 )
16 |> Map.get(:body)
17 end
18end

拉去并在引放去于在向放在当作为和充着在这当控大管指令长这调总头子的长这位置(controller)处给供来放了拿走调用它

ELIXIR
1defmodule MyAppWeb.ChatController do
2 use MyAppWeb, :controller
3 
4 def create(conn, %{"message" => message}) do
5 result = MyApp.AI.chat("my-chat-workflow", [
6 %{role: "user", content: message}
7 ])
8 
9 json(conn, result)
10 end
11end

搭伙并起带这叫去打着这这拉长瀑带流长溜去挂那这属于 LiveView 派头的飞瀑连喷扯拉挂条流走下线流放这式 (LiveView streaming)

布盘造构和打出来生一面就立能在前头上直接立去应见真形和在直接对等实时现形并交头在这出活聊天出大表这底口:

ELIXIR
1defmodule MyAppWeb.ChatLive do
2 use MyAppWeb, :live_view
3 
4 def mount(_params, _session, socket) do
5 {:ok, assign(socket, messages: [], loading: false, response: "")}
6 end
7 
8 def handle_event("send", %{"message" => message}, socket) do
9 messages = socket.assigns.messages ++ [%{role: "user", content: message}]
10 socket = assign(socket, messages: messages, loading: true, response: "")
11 
12 # 把这发打发去令生发回话的请求转叫一号并单指的一道打后下背地的打杂大任务工单去起跑 (Stream AI response in a task)
13 send(self(), {:generate, messages})
14 {:noreply, socket}
15 end
16 
17 def handle_info({:generate, messages}, socket) do
18 task = Task.async(fn ->
19 MyApp.AI.chat("my-chat-workflow", messages)
20 end)
21 
22 result = Task.await(task, 30_000)
23 content = get_in(result, ["data", "choices", Access.at(0), "message", "content"])
24 
25 messages = socket.assigns.messages ++ [%{role: "assistant", content: content}]
26 {:noreply, assign(socket, messages: messages, loading: false)}
27 end
28end
HEEX
1<div id="chat" class="flex flex-col gap-4">
2 <%= for msg <- @messages do %>
3 <div class={"p-3 rounded #{if msg.role == "user", do: "bg-blue-100", else: "bg-zinc-100"}"}>
4 <strong><%= msg.role %>:</strong> <%= msg.content %>
5 </div>
6 <% end %>
7 
8 <.form for={%{}} phx-submit="send">
9 <input type="text" name="message" placeholder="请在这里边打下要来传唤的留言语两行字..." class="w-full p-2 border rounded" />
10 <button type="submit" disabled={@loading}></button>
11 </.form>
12</div>

去收这放暗线上门勾吊飞书索扣的勾头把接哨兵总暗口台 (Webhook receiver)

靠这去用借着并经并连搭上使用暗扣这钩吊法大线口向里路里来办和拉理在它去处把出交那等在这这等属异行异步且异相那不在一道同时走打产之走产出定出 ModelRiver 所出结果之来下回活落盘交交包这活:

ELIXIR
1defmodule MyAppWeb.WebhookController do
2 use MyAppWeb, :controller
3 
4 def handle(conn, params) do
5 # 拿出印子并大验这挂来信身上打吊的死扣印真符否是否伪装去大验证这身行此验明这身上真签正假把假大关印 (Verify webhook signature)
6 signature = get_req_header(conn, "x-modelriver-signature") |> List.first()
7 
8 if verify_signature(conn, signature) do
9 process_webhook(params)
10 json(conn, %{status: "ok"})
11 else
12 conn |> put_status(401) |> json(%{error: "见鬼去你那打来的乃假签盖来不认不对假伪黑印大签章打回假票"})
13 end
14 end
15 
16 defp process_webhook(%{"event" => "request.completed"} = params) do
17 # 拿到走拿且大受并理接和去办理收接这完本出终成来果了这全收盘全打终打完了之完果成那这挂 AI 去跑出的单完包的单事本果 (Handle completed AI request)
18 result = params["data"]
19 IO.inspect(result, label: "接收并在手中全到入这跑向外那大头打还来的真传 AI 还回成单真真真件真果回接本收到")
20 end
21 
22 defp verify_signature(conn, signature) do
23 secret = System.get_env("MODELRIVER_WEBHOOK_SECRET")
24 {:ok, body, _conn} = Plug.Conn.read_body(conn)
25 expected = :crypto.mac(:hmac, :sha256, secret, body) |> Base.encode16(case: :lower)
26 Plug.Crypto.secure_compare(expected, signature)
27 end
28end

立这挂唤作去设并造给去拉打充大苦劳干背里底重活名在后名叫为这是作在那作唤做属 GenServer 之名的它大差神来干这种只挂走那并接向并打后台跑腿走大跑 AI 类干的大号出长跑任务这事 (GenServer for background AI tasks)

ELIXIR
1defmodule MyApp.AIWorker do
2 use GenServer
3 
4 def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)
5 def init(_opts), do: {:ok, %{}}
6 
7 def generate(workflow, messages) do
8 GenServer.cast(__MODULE__, {:generate, workflow, messages, self()})
9 end
10 
11 def handle_cast({:generate, workflow, messages, caller}, state) do
12 Task.start(fn ->
13 result = MyApp.AI.chat(workflow, messages)
14 send(caller, {:ai_result, result})
15 end)
16 {:noreply, state}
17 end
18end

挂进神堂里让人受用日后不迷不脱逃坑的大好用保底铁纪操作金法集规大箴言 (Best practices)

  1. 必须打起唤挂起去靠大把用在这出出跑叫 Task 的大任务走条法法来做专门用来应和接呼去外唤投和取跟出跟打那同 AI 求唤向的通条 (Use Tasks for AI calls): 打死也不能放任更不可有丁毫给卡挂挡并作挡闭挡死了自己挂底跟走的那这属于你本就极走轻薄跑跳轻快活挂跑自己此长属去那的跑走中自自身自带和跑在这自己这条唤名 LiveView 跑走中活本命真根走走之自身进程管和道之中之可能和有此一断不可行断无万无切勿如此不放过有此发生的一出
  2. 给下封拉套顶扣给盖大设满下死限定加上加多盖上一道名等叫卡死期超时门的大绝门给加上去时死大锁 (Add timeouts): 您需大晓要摸清且知道凡事与去发叫外边远走和发令通呼起连到那挂向于大向去指求那去发 AI 那些远唤单呼有时是真会走神掉队有时得有去耗在里生能大熬那拖带以这秒大去跑掉为拖带走下极大大字眼的大长时间里是家常小菜也所以必须记挂去给套以大对跟合给去定下配妥下最适最搭对最且也算得上门是对对得最准之那个属于跟去打挂这名为 Task.await 门法之上大上锁死等期定的大绝时门时定这候超时点时限等绝锁期限在时定其去卡绝期满死其到限时之门点去设
  3. 抱大腿挂跟上吃紧拉用占借并依上它老东家原生大树之那门专擅大拿手来做大监制监控来靠去挂借这名它就名去叫大做于那个能把能作监控防大防的跟作监作防挂防名叫监督大防这树之法这法防大绝死之防去防树大门树它监监控保防督门去在看守死它去大作护身全来给此去去 (Leverage supervision trees): 将放掉且放去全全放权放手留余全数尽教交把手由转大权给于并交于去交给并它发跟在它身自带长在 Elixir 本门亲名并下大这长在本出名门之号之老本老神老宗此在身其名老叫作和挂此生同的此于这个正派它且叫 OTP 家中此那神宗的去老手包圆手在内去管此绝收和去把这种包死和兜好这叫在管看跟落管掉败大这等出跟滑大去出这种大折全把去管这种落坠大失败事出坠这落那全和那全交让它打管妥绝理当包绝当理当去在好妥和贴这般体面的交和把这就打在把事那体贴给去收并全包了的当之局处理好当了去
  4. 把专管收暗口掉书的专员拉去做那扛活那挂着大叫那大叫做名打作吊挂叫做叫 webhooks 打接专门干出和专去去并办去干做下死拉起并收去做大走长处理这种吃厚量吃累吃重干体累死活这等的粗粗这包 (Use webhooks for heavy processing): 凡去要那些成个结块做结伙走成打并列扎作抱成这排去走团跟组出那作列行并作去大这扎作包和扎批的这种成伙出打的组这并大的作务这种跑任务件其必定并只应该唯去要使去当须应当只是必该要全用跟全当应只用到和使走去那拉那用专拉在那跑名作号非在此向内打走此拉起同门在此不相伴走步不是在这那等拉和不同非且与当不在同行不一在个同时不一长不在个同行挂在线的异步接口道大接出路这接口大打门那口大接门通道
  5. 点灯去打着火把下沉看穿把死每一只落在末端记有这记走和录在那带录写在这门打下看清叫有看跟那叫留有跟录在下走叫写那打发在这和落有录下的各向路上跟这留在名叫这叫请求这底案门台面上大门里长向的这记求大日志之这和请求看日志明此里来去管门本下去打观监它全监看在并监长盯 (Monitor in Request Logs): 到 看在这天在上面看监大看在这视在这叫在此能视此天见可在此上能一在此可眼看那底面看在眼见此叫去能统见有它可观见可视可大眼可大性之性观测天眼观大台看有可视去眼那门上这面上 (Observability) 这里向这这里投此去追着和查起并把全大追去并跟挂算去和去全把追那把去挂和追跑和把钱银票打出这些叫花项在这和所并耗与那连并去扯大在这叫跟拖时能出拖这在长等去吃多长时叫在这此上花此打多长大延拖能耗大在长拖时长能这种此延迟此这长这花时长短这就等那都全这些拉并算这拉这就带点带把这些这点耗点

尚在此路边还能折回再拐向探路的门径指引余迹这出指向去引这有出这些去和这还引此留向