AI 资讯
Texttile, a multiplayer blog engine for people who write together
This is a shortened version of my original post . My wife and I have blogged about every trip since our honeymoon 10 years ago. For the people at home, for ourselves later, and by now for our children. We took turns writing, but both had photos and videos on their phones. The text was never the hard part. The photos and videos were: every day one of us sent them from the phone to the other, who had to upload them and sort them into the entry in the right order. So I wrote Texttile , an open-source blog engine built for writing together. One entry, two screens Multiple people can have the same entry open. One of them has the text and types, the other watches the words arrive and can take the text over with one click. Both can still work on the gallery. Photos and videos belong in the same gallery. Videos come from your own server. Drop one in and Texttile converts it, thumbnail included. No YouTube embed, no player from anywhere else. One container, one folder Phoenix, LiveView, ffmpeg and SQLite live in one Docker image. Everything is in /data . Move that folder and you move the blog. A reader's browser talks to your server and nothing else. No CDN, no tracker, no hosted font. What it is not There are no roles, no permission matrix, no plugins, no theme marketplace. Everybody with an account is an admin. I built it for people who trust each other, because that is who writes a blog together. You can try it or read the source . The full story includes a video showing both screens. How do you blog on the road?
AI 资讯
Learning Elixir: Private vs Public Functions
Clients of a house only see the facade: the door, the windows, the porch. The boiler closet, the fuse box, and the plumbing stay out of sight — and that is exactly what lets someone rearrange them without telling anyone. An Elixir module works the same way. The functions other modules call are the facade; helpers marked defp are the boiler closet. In the previous article about project structure we agreed that Todo is the front door of the todo feature and Parser is a back room. Nothing enforced that, though: every function we wrote with def was public, so any module could call Parser directly. defp turns the "back room" sign into an actual lock. In this article I'll explore the difference between def and defp , what privacy actually protects, and the patterns I use to keep our todo modules' interfaces small. Note : The examples in this article use Elixir 1.20.1. While most operations should work across different versions, some functionality might vary. We keep building on the same learning_elixir Mix project from the previous article ( Learning Elixir: Project Structure ). If you no longer have it, recreate it with mix new learning_elixir and add the Todo modules back — we'll show every file in full anyway. Two details that keep showing up: modules live under lib/ , and multi-step scripts like try_todo.exs live at the project root, next to mix.exs . Short checks run inline with mix run -e '...' , no file needed, and iex -S mix opens the same project in the shell for quick experiments. One more note on the outputs: mix prints a Compiling N files (.ex) banner before running, and the count depends on your build cache. I trimmed that line from most examples below. Table of Contents Introduction The Difference: def and defp What Privacy Actually Protects Refactoring the Todo Project Privacy Is Per Module, Not Per Namespace Multiple Clauses: One Name, One Visibility Docs, Specs, and Private Functions @doc false: Public but Hidden Reading a Module's Surface Import Only Bri
AI 资讯
Learning Elixir: Project Structure
When an Elixir project outgrows a handful of modules, the habit of pasting definitions into IEx starts to break down — redefinition warnings pile up, names collide, and nothing survives the session. One way to picture what comes next is a workshop: it begins as a single shelf of drawers and, as the work grows, turns into a whole room with labeled aisles and a storage room for materials that are not tools at all. In the last few articles I filled those drawers — modules — and learned how to reach between them with alias , import , and require . A project structure is the map of that room: it tells me where each module lives on disk, how file names line up with module names, and which aisles other people are allowed to walk into. In this article, we will build a real Mix project from scratch and watch it grow: starting from mix new , adding directories like lib , test , priv , and config , naming modules so they mirror their paths, and drawing boundaries so the project stays navigable as it grows. Note : The examples in this article use Elixir 1.20.1. While most operations should work across different versions, some functionality might vary. This is also a small turning point for the series: instead of pasting examples into iex , everything from now on lives in real files inside a Mix project. Each code block shows the file path, and every example is verified with mix run , with the output shown right after — and when we just want to poke at our functions, iex -S mix brings back the interactive shell, now with the project compiled from its files. Table of Contents Introduction Starting From Mix New The Anatomy of the Default Project Growing Lib: Modules That Mirror Paths Running the Project: Scripts and the Shell Namespaces and Boundaries The Priv Directory Configuration and Environments Practical Guidelines Conclusion Further Reading Next Steps Introduction In the previous articles, every example was self-contained: one or two defmodule blocks pasted into an iex sess
AI 资讯
Orquestração de Agentes de IA no Direito: Construindo Workflows de Triagem e Resumo de Casos sem Perder a Validação Humana
A inteligência artificial no setor jurídico ultrapassou a fase dos chatbots genéricos de pergunta e resposta. Quando lidamos com o Direito, o custo de uma "alucinação" de IA não é apenas um incômodo — pode significar a perda de um prazo fatal, uma tese fundamentada em jurisprudência inexistente ou a violação de sigilo. Para resolver esse problema, a engenharia de software aplicada a LegalTechs está migrando para os Agentic AI Workflows (Workflows de IA Agêntica). Em vez de depender de um único prompt gigantesco para resolver um problema complexo, orquestramos múltiplos agentes especializados. Neste artigo, vamos detalhar como arquitetar uma esteira de triagem, busca vetorial e sumarização de processos, utilizando ferramentas maduras e garantindo que o advogado permaneça como o orquestrador final no Quality Gate . 1. Dividir para Conquistar: A Arquitetura Multi-Agente A premissa da orquestração de agentes é a especialização. Cada agente no sistema possui um escopo restrito, ferramentas específicas ( tool use ) e um objetivo claro. Em um cenário de entrada de um novo processo longo (ex: um PDF de 500 páginas), o workflow se divide em três estágios: Agente 1: Classificação de Intenção e Roteamento O primeiro agente atua como o recepcionista. Ele não lê o documento para extrair teses; ele apenas analisa as primeiras páginas para responder: O que é isso? É uma Inicial Trabalhista? Uma intimação de prazo? Uma contestação? A partir dessa classificação, o workflow roteia o documento para a fila correta de processamento. Agente 2: RAG (Retrieval-Augmented Generation) e Busca Vetorial O segundo agente é o pesquisador. Ele quebra o documento em fragmentos ( chunks ) e cruza as alegações da parte contrária com o acervo interno do escritório. No ecossistema Elixir, por exemplo, podemos utilizar o PostgreSQL com pgvector e Ecto para armazenar os embeddings de casos passados e jurisprudências vencedoras do próprio escritório. O agente busca semelhanças e recupera o contexto estrit
AI 资讯
Building Distributed Systems in Elixir: Part 6 — Named Processes
In the previous part of this series, we built a tiny supervisor from scratch. When a worker crashed, the supervisor started a replacement. That replacement had a new PID: old worker -> #PID<0.102.0> new worker -> #PID<0.105.0> This reveals an important limitation of sharing PIDs as a public interface. A PID identifies one running incarnation of a process. It is excellent for sending a reply, setting up a monitor, or creating a link. It is not a stable address for a service that may stop and later be replaced. In this part, we'll use named processes to give a worker a discoverable address: :worker We'll build three small examples using: Process . register / 2 Process . whereis / 1 :global . register_name / 2 :global . whereis_name / 1 send / 2 No GenServer . No OTP Registry . The goal is to understand the lookup problem that registries solve before reaching for those abstractions. The PID-Sharing Problem Suppose one process starts a worker and gives its PID to a client: worker = spawn ( fn -> worker_loop () end ) send ( client , { :worker_started , worker }) The client can now send work directly: send ( worker , { :work , self (), "hello" }) This works while that particular worker process is alive. But process IDs are temporary. If the worker exits, the PID is no longer a route to the service: Client Worker holds #PID<0.102.0> #PID<0.102.0> | | | X exits | | send(#PID<0.102.0>, work) |------------------------------> no worker receives it Sending to a dead local PID does not raise an error and does not restart a process. The message is simply not delivered to a living worker. One answer is to tell every client about every new PID after a restart. That spreads lifecycle knowledge throughout the system. Another answer is to make clients depend on a name and resolve that name when sending. Registering a Local Name Our first worker waits for a stop message: defmodule Worker do def start do spawn ( fn -> receive do :stop -> :ok end end ) end end Starting it gives us a PID:
AI 资讯
Zenoh's put is fire-and-forget, get isn't — a read-after-write race in Elixir
This English version is an AI translation of my original article on Qiita (in Japanese) . Background I've been experimenting with Zenoh via its Elixir bindings, Zenohex , not for its usual pub/sub use case but for its put / get storage feature. It mostly worked, except every so the state I picked back up was one step behind. Digging into why turned into a fun rabbit hole, so here's the writeup. Reproducing it To keep things simple, strip out the GenServer part entirely and just loop put immediately followed by get on the same key: { :ok , session_id } = Zenohex . Session . open ( config ) Enum . each ( 1 .. 2000 , fn i -> payload = Integer . to_string ( i ) :ok = Zenohex . Session . put ( session_id , key , payload ) { :ok , replies } = Zenohex . Session . get ( session_id , key , 3_000 , consolidation: :latest ) case Enum . find ( replies , & match? (% Zenohex . Sample {}, &1 )) do % Zenohex . Sample { payload: ^ payload } -> :ok % Zenohex . Sample { payload: other } -> IO . puts ( "stale! put #{ payload } but got #{ other } " ) nil -> IO . puts ( "no reply at all" ) end end ) Out of 2000 iterations, a small fraction print stale! — about 78 (3.9%) in one run. The interesting part: querying again immediately afterward almost always returns the correct value (the fastest I measured was a single extra get about 1ms later). So it's not that the value disappears — there's just a small window of lag before the write is actually visible. Why Zenohex.Session.put/4 is a thin Rustler wrapper around zenoh-rust's put . Looking at the NIF implementation : fn session_put ( ... ) -> rustler :: NifResult < rustler :: Atom > { ... publication_builder .apply_opts ( opts ) ? .wait () // <- only waits for the local publish to be queued ... Ok ( rustler :: types :: atom :: ok ()) } .wait() only waits for the local session to finish handing the message off — not for the remote side (the zenohd router backing the storage) to actually receive and apply it. session_get , on the other hand,
开源项目
🔥 teslamate-org / teslamate - A self-hosted data logger for your Tesla 🚘 [main maintainer=
GitHub热门项目 | A self-hosted data logger for your Tesla 🚘 [main maintainer=@JakobLichterfeld] | Stars: 8,147 | 35 stars today | 语言: Elixir