Skip to content
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

Add Depends like behaviour #32

Closed
wants to merge 2 commits into from
Closed

Add Depends like behaviour #32

wants to merge 2 commits into from

Conversation

dmontagu
Copy link
Contributor

@dmontagu dmontagu commented Nov 8, 2024

Can't believe it but it looks like this passes all tests and type-checking.

We'd need to do the same for the the other kinds of things that can be done with decorators but this seems pretty nice. Note that this requires CallContext to work (can't use bare Deps), so I think it's probably best to keep that around.

Copy link
Member

@samuelcolvin samuelcolvin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall LGTM, needs some tests.

# Annotated with Depends(...)
new_params.append(param)
continue
type_hint = annotated_args[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure we need this, I worry it opens up some potentially annoying precedent to allow Annotated[CallContext(..), ...].

Callable[P, T],
_InjectWrapper[P, T],
]:
"""Basically the same as `fast_depends.Depends`, just adds a marker indicating that it was called."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Basically the same as `fast_depends.Depends`, just adds a marker indicating that it was called."""
"""Basically the same as `fast_depends.inject`, just adds a marker indicating that it was called."""

return result


def is_injected(func: Callable[..., Any]) -> bool:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably want to mark this as private, it shouldn't be in the public API.

@samuelcolvin
Copy link
Member

presumably we have to relax the signature of @agent.system_prompt, and add @agent.system_prompt_context which has the current type checking.

P = ParamSpec('P')
T = TypeVar('T')

DependsType = model.Depends
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should have an __all__. and Depends at least should be re-exported from __init__.py.

Copy link

cloudflare-workers-and-pages bot commented Nov 12, 2024

Deploying pydantic-ai with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2b353d5
Status: ✅  Deploy successful!
Preview URL: https://af114120.pydantic-ai.pages.dev
Branch Preview URL: https://use-fast-depends.pydantic-ai.pages.dev

View logs

@dmontagu dmontagu changed the title Use fast-depends for supporting dependency injection for system prompts Add support for dependency injection for agent decorators Nov 12, 2024
Copy link
Member

@samuelcolvin samuelcolvin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this looks like a good start, my main concern that there's a lot more work to do.

I also wonder if we should add a way to customise Deps similar to dependency_provider.scope but specifically for accessing them from CallContext. Something like

with pydantic_ai.override_deps(MyAgent, custom_deps):
    ...

I think next steps (in order of priority):

  1. make sure Depends is working in system prompts, retrievers and result validation
  2. add tests
  3. get the API right so stuff we don't want to expose is private
  4. add thorough docstrings

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest we move /_depends to /depends to make it public, then only export Depends form the root __init__.py

Making this file public should also make the API documentation easier.

@@ -0,0 +1,118 @@
import inspect
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one line docstring would be nice.

use_cache: bool = True,
is_sync: bool | None = None,
) -> CallModel[P, T]:
name = getattr(call, '__name__', type(call).__name__)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we assume elsewhere that call.__name__ is always available for callables.

for arg in annotated_args[1:]:
if isinstance(arg, DependsType):
if dep:
raise ValueError(f'Cannot specify multiple `Depends` arguments for `{param_name}`!')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably use our UserError exception, or remove it and use plain ValueError everywhere.

*,
use_cache: bool = True,
is_sync: bool | None = None,
) -> CallModel[P, T]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quick docstring even if this is private.

# Define the response type
@dataclass
class ReviewAnalysisResponse:
score: int # Between -5 (most negative) and +5 (most positive)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constraint, also use Field to add a description to help the LLM.

@dataclass
class ReviewAnalysisResponse:
score: int # Between -5 (most negative) and +5 (most positive)
tags: list[ReviewTag] # Extracted tags from the review
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

description again.

tags: list[ReviewTag] # Extracted tags from the review


Response = ReviewAnalysisResponse
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this?

Comment on lines +34 to +37
agent = Agent[None, Response](
result_type=Response,
retries=2,
model=GeminiModel('gemini-1.5-flash', api_key='<OMITTED>'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
agent = Agent[None, Response](
result_type=Response,
retries=2,
model=GeminiModel('gemini-1.5-flash', api_key='<OMITTED>'),
agent = Agent(
'gemini-1.5-flash'
result_type=Response,
retries=2,

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The principle looks good, I think we need do a better job of separating the production code from the evals code.

@samuelcolvin samuelcolvin changed the title Add support for dependency injection for agent decorators Add Depends like behaviour Nov 12, 2024
@dmontagu
Copy link
Contributor Author

Closing in favor of alternative approaches, just too much to add here

@dmontagu dmontagu closed this Nov 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants