fenic: semantic DataFrames where LLM operators are query operators
Semantic DataFrames for humans and agents
At a glance
- What is it?
- fenic adds extract, classify, summarize, embed and semantic join to a PySpark-style DataFrame API, so model calls become typed columns inside a rerunnable plan. The design is coherent; the interesting question is what you give up when inference moves inside the query engine.
- Who is it for?
- Adopt fenic if your work is repeated extraction or classification over tickets, transcripts, logs or eval traces, and you want the prompt logic to live in version-controlled Python rather than in a notebook or a chat transcript. Do not adopt it for one-off analysis of a single file, or for pipelines where a deterministic regex already holds and a model call would add cost and latency for nothing.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem fenic targets: prompt chains that evaporate
The README frames the problem in two moves. First, unstructured data work is brittle: teams reach for regex, one-off scripts, notebooks and prompt chains, and the results are hard to reproduce and hard to inspect. Second, agents made exploration easy and introduced a different failure: an agent can dig through messy data and find something useful, but unless that discovery becomes code, data or a pipeline, it dies as a chat transcript and the next person has to reverse-engineer what happened. That second framing is the sharper one, and it is where fenic positions itself. The audience is a team that already uses coding agents against semi-structured data (eval traces, support tickets, logs, transcripts) and has felt the cost of a discovery that nobody can rerun. The pitch is that the exploration itself becomes the artifact, because it was written in operators rather than in prose. Whether that holds depends on whether the operator set covers what your exploration actually did, which is a narrower claim than the README's table implies.
How the semantic operators fit into the query plan
fenic is a DataFrame query engine with AI operators built into the query model rather than bolted alongside it. You write the PySpark and SQL-style operations the README lists (`select`, `filter`, `join`, `group_by`, `agg`) next to semantic operators: `extract`, `classify`, `summarize`, `embed`, and a semantic `join`. Models are configured once on a `Session` via `fc.SessionConfig`, and the pipeline is built lazily. fenic then compiles and runs it on an engine that handles automatic batching, rate limiting, retries, token and cost accounting, and response caching. The README's own summary of the difference from gluing an LLM onto pandas is that inference lives inside the query model: extraction, classification, summarization and embeddings are operators with schemas and types, not side calls you orchestrate by hand. The second claim is that the pipeline is the artifact, which is what makes row-level lineage, `explain`, per-query metrics, and promotion into a named table, view, or MCP tool meaningful. The honest reading is that fenic is a scheduler and type system wrapped around model calls. The model is still probabilistic; the README says so directly. What the engine controls is everything around the call.
Schema-bound extraction, shown in the quickstart
The concrete mechanism is worth reading closely because it is where the design either fits your work or does not. You define the shape you want as a Pydantic model, then call `fc.semantic.extract` against a text column and unnest the result. The README's quickstart defines a `Ticket` model with `product`, `sentiment` and `issue` fields, each carrying a `Field(description=...)`, and runs it over two rows of support text. The displayed output is a table with columns `id`, `product`, `sentiment`, `issue` and rows such as `1 | Reports | negative | CSV export times out`. Two details in that example carry weight. The field descriptions are part of the contract sent to the model, so schema quality is prompt quality. And the extraction is validated at plan time according to the README's comparison table, which is the claim that separates this from a helper function that parses JSON out of a completion. The second featured example is eval analysis: a `FailureMode` model with a `failed` boolean, a `category` field typed as a `Literal` over `tool_error`, `instruction_following`, `retrieval`, `reasoning` and `none`, and an `evidence` string. Constraining a category to a `Literal` is the pattern that makes downstream `group_by` and `agg` possible without cleaning free text first.
Getting a pipeline to run: install, keys, session config
Installation is a single command: `pip install fenic`. Provider credentials come from environment variables, and the README lists `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, `COHERE_API_KEY` and `OPENROUTER_API_KEY`. The session is created with `fc.Session.get_or_create(fc.SessionConfig(...))`, where `app_name` names the session and `semantic=fc.SemanticConfig(language_models={...})` maps a short alias to a model object. The quickstart uses `"mini": fc.OpenAILanguageModel(model_name="gpt-4o-mini", rpm=500, tpm=200_000)`, which shows that rate limits are declared in the model configuration rather than discovered through failures. That alias is then what the semantic operators reference. For teams using coding agents, the README documents two CLI commands: `fenic skill install`, which installs instructions so Claude Code, Cursor and Codex write fenic correctly, and `fenic check`, which lints fenic code. Both are worth running before you trust generated pipeline code, since the failure mode of an agent writing plausible-looking operators against a schema-bound API is a pipeline that runs and extracts the wrong thing.
What the engine does not do for you
The README is explicit that the model remains probabilistic, and that is the limitation that matters most. Typed columns and plan-time validation constrain the shape of the output, not its correctness. A `sentiment` field will always be a string; nothing in the material says it will be the right string. Cost and latency are the second constraint. Every semantic operator is a model call, and the engine's batching, rate limiting and caching manage those calls rather than remove them. A pipeline that extracts three fields from a hundred thousand rows is a hundred thousand model calls minus whatever the cache absorbs, and the README does not state cache invalidation behaviour, so rerunning after a prompt or schema change is the case to reason about before you schedule anything. Third, the material does not establish how the semantic `join` decides that two rows match. That operator is listed but never demonstrated, and join semantics are exactly where a probabilistic operator is hardest to reason about, because a wrong match is silent. Treat it as unverified until you have read the source or the docs. Finally, the README's comparison table is a positioning argument, not evidence, and nothing in the supplied material reports accuracy, throughput or cost figures.
Compared with an orchestration framework plus a DataFrame library
The realistic alternative is composing this yourself: pandas or Polars for the tabular work, plus an orchestration layer such as LangChain or DSPy for the model calls, with a cache and a retry policy you write. The difference is where the boundary sits. In that stack, the DataFrame library knows nothing about the model call; you loop over rows or batches, parse the completion, coerce it into a column, and handle failures in your own code. In fenic, the model call is an operator with a schema, and batching, rate limiting, retries, token accounting and caching are properties of the plan rather than of your loop. That matters most when the pipeline is long-lived and someone else has to read it. It matters least when you have one prompt and one column, where an orchestration framework plus pandas is less machinery for the same result. The second alternative is the one the README argues against implicitly: a coding agent plus a notebook. That is faster to start and produces nothing rerunnable, which is precisely the failure fenic is built around.
Maintenance, releases and the Apache-2.0 terms
fenic is Apache-2.0 and the repository is not archived. The release cadence visible in the supplied material is roughly every two to three weeks: v0.11.0 on 2026-07-15, v0.12.0 on 2026-07-29, v0.13.0 on 2026-08-18, with the last push to `main` on 2026-09-08. The version numbers are still in the 0.x range, which is the practical signal here: minor releases can carry breaking changes under semantic versioning conventions, so pinning a version in your dependency file and reading the release notes before upgrading is the cheap insurance. The cost side of maintenance is not the library, it is the prompts. Pydantic field descriptions and `Literal` category sets are code that shapes model output, so changing a schema is a behaviour change that should be tested the way any other behaviour change is. On licensing, Apache-2.0 permits commercial use and modification and includes a patent grant; it also requires that you preserve the licence and notices. That is a plain description of the terms, not legal advice, and if you are redistributing fenic inside a product, have counsel read the LICENSE file rather than this paragraph.
Editorial conclusion
Adopt fenic if your work is repeated extraction or classification over tickets, transcripts, logs or eval traces, and you want the prompt logic to live in version-controlled Python rather than in a notebook or a chat transcript. Do not adopt it for one-off analysis of a single file, or for pipelines where a deterministic regex already holds and a model call would add cost and latency for nothing. Before committing, verify three things against your own data: whether the Pydantic schema you need survives plan-time validation, whether the semantic join operator's matching behaviour is acceptable on your key distributions, and whether the caching and cost accounting in your installed version behave the way the README describes. Run `fenic check` on a real pipeline first.
Community notes