-
Notifications
You must be signed in to change notification settings - Fork 166
chore: Xin/improve unnamed runtree naming #2124
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| """Schemas for the LangSmith API.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
@@ -233,13 +233,53 @@ | |
| @root_validator(pre=True) | ||
| def infer_defaults(cls, values: dict) -> dict: | ||
| """Assign name to the run.""" | ||
| # Try to get name from serialized data (legacy LangChain support) | ||
| if values.get("name") is None and values.get("serialized") is not None: | ||
| if "name" in values["serialized"]: | ||
| values["name"] = values["serialized"]["name"] | ||
| elif "id" in values["serialized"]: | ||
| values["name"] = values["serialized"]["id"][-1] | ||
| serialized = values["serialized"] | ||
| values["name"] = serialized.get("name") or ( | ||
| serialized["id"][-1] if "id" in serialized else None | ||
| ) | ||
|
|
||
| # If still no name, try to infer from caller | ||
| if values.get("name") is None: | ||
| values["name"] = "Unnamed" | ||
| name = None | ||
| try: | ||
| import inspect | ||
| import os | ||
|
|
||
| # Look up the stack, skipping internal frames | ||
| for frame_info in inspect.stack()[1:8]: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any perf concerns here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it should be rare case we even need to enter "make a name" mode. i.e, this conditoin should not be entered. |
||
| filename = frame_info.filename | ||
| norm_path = filename.replace("\\", "/") | ||
|
|
||
| # Skip pydantic and langsmith internal modules | ||
| if "/pydantic/" in norm_path: | ||
| continue | ||
| if os.path.basename(filename) in { | ||
| "run_trees.py", | ||
| "run_helpers.py", | ||
| "client.py", | ||
| "_context.py", | ||
| }: | ||
| continue | ||
|
|
||
| # Try to get class name from self or cls | ||
| frame_locals = frame_info.frame.f_locals | ||
| if "self" in frame_locals: | ||
| name = frame_locals["self"].__class__.__name__ | ||
| break | ||
| elif "cls" in frame_locals: | ||
| name = frame_locals["cls"].__name__ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know enough about Python to know if this is a good idea Is there no easier fallback?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's try catch so at least it won't error, no better fallback afaik |
||
| break | ||
| except Exception: | ||
| pass | ||
|
|
||
| # Use inferred name or descriptive fallback | ||
| if name: | ||
| values["name"] = name | ||
| else: | ||
| run_type = values.get("run_type", "chain") | ||
| values["name"] = f"Unnamed_{run_type}" | ||
| if "client" in values: # Handle user-constructed clients | ||
| values["ls_client"] = values.pop("client") | ||
| elif "_client" in values: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| """Test name inference for RunTree when name is not provided.""" | ||
|
|
||
| from unittest import mock | ||
|
|
||
| from langsmith import Client, traceable | ||
| from langsmith.run_trees import RunTree | ||
|
|
||
|
|
||
| class Router: | ||
| """Simple class with __call__ method (like LangGraph nodes).""" | ||
|
|
||
| def __call__(self, state: dict) -> dict: | ||
| return {"result": "ok"} | ||
|
|
||
|
|
||
| def test_runtree_infers_class_name_from_stack(): | ||
| """RunTree should infer class name from stack when created in class method.""" | ||
|
|
||
| class MyRouter: | ||
| def process(self): | ||
| return RunTree(run_type="chain") | ||
|
|
||
| run = MyRouter().process() | ||
| assert run.name == "MyRouter" | ||
|
|
||
|
|
||
| def test_runtree_falls_back_to_descriptive_name(): | ||
| """RunTree should use Unnamed_{run_type} when inference fails.""" | ||
| run = RunTree(run_type="llm") | ||
|
|
||
| # Should NOT be just "Unnamed" - should be descriptive or inferred | ||
| assert run.name != "Unnamed" | ||
| # Name should be something reasonable (inferred from stack or fallback) | ||
| assert len(run.name) > 0 | ||
|
|
||
|
|
||
| def test_runtree_uses_explicit_name(): | ||
| """RunTree should use explicit name when provided.""" | ||
| run = RunTree(name="MyExplicitName", run_type="chain") | ||
| assert run.name == "MyExplicitName" | ||
|
|
||
|
|
||
| def test_utils_get_function_name_for_callable_class(): | ||
| """_get_function_name should return class name for callable instances.""" | ||
| from langsmith.utils import _get_function_name | ||
|
|
||
| router = Router() | ||
| assert _get_function_name(router) == "Router" | ||
|
|
||
|
|
||
| def test_traceable_with_callable_class(): | ||
| """@traceable should work with callable classes.""" | ||
| router = Router() | ||
| traced = traceable(router) | ||
|
|
||
| with mock.patch.object(Client, "create_run") as mock_create: | ||
| traced({"input": "test"}) | ||
|
|
||
| if mock_create.called: | ||
| name = mock_create.call_args[1].get("name", "") | ||
| assert name == "Router" | ||
| assert name != "Unnamed" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Imports like this should be top level right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it should be rare case we even need to enter "make a name" mode. so i don't worry too much about perf.
yeah inspect can go to top