-
Notifications
You must be signed in to change notification settings - Fork 105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: added a way to wrap existing models with additional middleware #451
base: main
Are you sure you want to change the base?
Conversation
What's the reasoning behind this instead of your first PR? I can see instances where I'd want to use each - middleware more oriented at specifics of the prompt would make sense to pass with If the question is around observability/tracing, I think we have some work to do there - I think |
The problem is with the playground -- middleware passed into the One way to work around it would be to treat middleware similar to how we do tools -- we can register middleware in the registry ( |
I think there's probably an "all of the above" here but I think it would be perfectly reasonable to ship direct-use middleware without playground support (you can still test it out by wrapping in a flow) and it would unlock the most flexibility for users. We could expand that to named/defined middleware as an "also" not an "instead of" which would potentially open up Dotprompt support: middleware:
- name: SomeMiddleware
args: {a: 123} |
I very very strongly oppose the idea of direct use of middleware in the generate function. It undermines and breaks the core idea of trace + playground combo. It does it in very subtle way that will leave developers confused. I mean it won't throw errors or anything obvious, it will return different results (one way will apply middleware the other won't). |
I'm confused...if I supply an output schema that also returns different results than if I don't. What's the scenario where this causes confusion? If I call If I'm in a playground and can't supply middleware, it's obviously not going to have the effects of that middleware applied. If I define a flow that includes a I think "everything you can do with |
In my dream scenario, a trace with middleware would have steps that look something like: generate({
model: gemini15Flash,
prompt: "Do cool stuff.",
use: [myMiddleware, mySecondMiddleware],
});
|
You are definitely thinking outside the box here. It's not how we do it today. I mean, yeah, if we break up the trace for the generate function like that it solves the main problem I'm talking about, but the more middleware you put directly into the generate function the less useful Genkit tooling becomes. Where's the line? How do you determine what's OK to call directly via If the developer wants to build advanced features they should be building them on top of |
What's the latest status of this conversation? |
I think at the moment we're kind of at an impasse. I see middleware as being one of the most powerful yet simple-to-consume abstractions in software but I think my ❤️ is not shared by most people. The in-use comparison is basically: await generate({
prompt: promptOrSomething,
use: [myMiddleware({option1: true}), otherMiddleware()]
}); vs. const before1 = await myNotMiddleware({option1: true}, promptOrSomething);
const before2 = await otherMiddleware(before1);
const unprocessedResult = await generate(before2);
// maybe you also did something after the request in one of the middlewares
const result = await myNotMiddlewareAfter({option1: true}, unprocessedResult); Middleware is robustly configurable and allows for complex transformation in a simple package, but is harder to conceptualize for the people who write it (until they manage to completely grok the pattern at which point it becomes very straightforward, but this admittedly is not most devs).
This is only true if we don't decide to make middleware a key part of Genkit including the tooling. I think adding middleware as a registry-backed primitive with full support in the DevUI is totally doable and would actually be really cool! Imagine if the model playground just let you add extra middleware like configurable retries or any number of things and then you can just export it to a dotprompt or code. |
+1 to a registry-backed primitive. I haven not yet heard it discussed, but I think there are some nice possibilities for 3P middleware if we open it up. There are "generative" behaviors that not every model implements (such as system prompt) that we already express via middleware, and recently wrapping models w/ tool calling came up as something we should probably have (e.g. for ollama). Wouldn't it be cool if that were a middleware that could be retroactively applied by the developer to models that don't support it out of the box. |
There are 2 ways to use middleware:
I think those two ways are kind of orthogonal. I think (1) should be at least one of the ways, I don't see why not. |
The idea is to be able to do things like