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

feat: python API for feedback #1723

Merged
merged 5 commits into from
Jun 17, 2024
Merged

Conversation

jamie-rasmussen
Copy link
Contributor

@jamie-rasmussen jamie-rasmussen commented Jun 6, 2024

Adds feedback APIs to the Python SDK.

Note: So far I have only added the feedback object to Calls and added querying for feedback at the project level. We would want to add feedback to objects like Datasets in a subsequent PR.

Some examples of use:

Screenshot 2024-06-06 at 4 26 22 PM Screenshot 2024-06-06 at 4 28 39 PM Screenshot 2024-06-06 at 4 37 02 PM

@circle-job-mirror
Copy link

@jamie-rasmussen jamie-rasmussen force-pushed the jamie/feedback-python-api branch 2 times, most recently from 686acf6 to 2e75532 Compare June 6, 2024 21:44
@jamie-rasmussen jamie-rasmussen marked this pull request as ready for review June 6, 2024 22:18
@jamie-rasmussen jamie-rasmussen requested a review from a team as a code owner June 6, 2024 22:18
@jamie-rasmussen jamie-rasmussen force-pushed the jamie/feedback-python-api branch 2 times, most recently from edf2926 to 0792922 Compare June 13, 2024 00:45
reaction: Optional[str] = None,
offset: int = 0,
limit: int = 100,
) -> Feedbacks:
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please add documentation to this method? I see two things that might be confusing for me:

  1. query is implemented as query_or_feedback_id. I think you made this decision in order to make the first positional argument dual purpose and therefore nicer syntax. Anyway, would be good to make that clear
  2. reaction -> is this supposed to be the literal emoji or shorthand? Both? Good to document

weave/pydantic_util.py Outdated Show resolved Hide resolved
if self.items:
self.items = [f for f in self.items if f.id != feedback_id]

def _repr_pretty_(self, p: Any, cycle: bool) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

is this automatically called by ipython? maybe i am unfamiliar with is stub?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this is called automatically by ipython. https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display

Shawn called out this pretty repr stuff as the bit he's most likely to want to change for consistency across APIs, but he was OK with it going in while he's trying out different possibilities.

query=Query(
**{
"$expr": {
"$eq": [
Copy link
Contributor

Choose a reason for hiding this comment

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

might be nice if this also included the ref in the query expression. Else, you could inadvertently purge an id that does not belong to that ref

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, I'm going to defer this - it's a good idea, but I'd have to first update the server side APIs, because also in the name of safety we put in restrictions on the purge endpoint that the purge query must be only a ref or list of refs.

from weave.trace.refs import parse_uri


class Feedbacks:
Copy link
Contributor

Choose a reason for hiding this comment

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

I am seeing that we have 4 data structures:

  1. tsi.Feedback -> The common Feedback data pydantic schema
  2. Feedbacks -> A container of tsi.Feedbacks
    • used primarily for display
    • returned from RefFeedback.query
    • Implements an iterator protocol over tsi.Feedback
  3. RefFeedback -> A "pointer" to feedback for a refable thing.
    • Implements an iterator protocol over tsi.Feedback
    • Lazily fetches data
    • Supports mutation operations (because it is relates to a specific "ref")
  4. Implied: Iterator[tsi.Feedback]

I'm not sure exactly why, but Feedbacks and RefFeedback seem to overlap in an important way.

Ok, i think either:

  1. RefFeedback wants to inherit from Feedbacks
  2. RefFeedback should contain a Feedbacks instead of items

For some reason i also feel like they are the same thing. RefFeedback and Feedbacks both can be thought of as FeedbackQuery. I think the only difference is that RefFeedback has the added property that you can add/purge. The other difference is that Feedbacks is pre-materialized, but i think you could also make Feedbacks lazy.

Ok, talking this out, consider changing to two classes: FeedbackQuery and RefFeedbackQuery(FeedbackQuery).

FeedbackQuery basically is merging of both RefFeedback and Feedbacks (except for add/purge). It is lazy, iterator, nicely prints, etc... It has an internal _query property holding the current query. It also has a query method that returns a new FeedbackQuery with the query append to the existing query.

Then, RefFeedbackQuery is a subclass with an additional weave_ref property. This is where add/purge goes.

The result of this is that the laziness/items cache/printing/iteration/query building/execution are all centralized and only the specific abilities of the RefFeedback are pulled out to the other class.

return Refs(ref for ref in self.items if isinstance(parse_uri(ref), CallRef))

# TODO: Perhaps there should be a Calls that extends AbstractRichContainer
def calls(self) -> list[TraceObject]:
Copy link
Contributor

Choose a reason for hiding this comment

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

2 notes:

  1. This could be optimized by grouping all the ids together then executing a single request instead of N network requests
  2. I feel like this wants to be on FeedbackQuery itself, so that my code is:
client.feedback(...).calls()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, can definitely address these in a followup.

Copy link
Contributor

@tssweeney tssweeney left a comment

Choose a reason for hiding this comment

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

Approving, with comments. I think it would be good to get this in an keep iterating if need

@jamie-rasmussen jamie-rasmussen merged commit 555933f into master Jun 17, 2024
24 checks passed
@jamie-rasmussen jamie-rasmussen deleted the jamie/feedback-python-api branch June 17, 2024 16:51
@github-actions github-actions bot locked and limited conversation to collaborators Jun 17, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants