Skip to content

Commit 9db9068

Browse files
committed
fix: improve asset search in lookup with term extraction
Previous: used full query as search pattern (e.g., *MeshRenderer 2D lights URP*) — matched nothing. Now extracts individual terms, strips stopwords, infers filter_type from keywords (shader→Shader, etc.), runs multiple focused searches in parallel. Deduplicates results.
1 parent d745c10 commit 9db9068

2 files changed

Lines changed: 105 additions & 17 deletions

File tree

Server/src/services/tools/unity_docs.py

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,54 @@ async def _get_package_doc(
454454
}
455455

456456

457+
# Words to skip when building asset search patterns
458+
_ASSET_STOPWORDS = {
459+
"in", "the", "a", "an", "to", "for", "of", "on", "with", "how", "can", "do",
460+
"i", "my", "is", "it", "this", "that", "unity", "objects", "object", "using",
461+
"receive", "make", "apply", "get", "set", "use", "create",
462+
}
463+
464+
# Map keywords to Unity asset filter types
465+
_KEYWORD_TO_FILTER_TYPE = {
466+
"shader": "Shader", "shaders": "Shader", "lit": "Shader", "unlit": "Shader",
467+
"material": "Material", "materials": "Material", "mat": "Material",
468+
"texture": "Texture2D", "textures": "Texture2D", "tex": "Texture2D",
469+
"sprite": "Sprite", "sprites": "Sprite",
470+
"prefab": "Prefab", "prefabs": "Prefab",
471+
"mesh": "Mesh", "model": "Mesh",
472+
"font": "Font", "fonts": "Font",
473+
}
474+
475+
476+
def _build_asset_search_terms(query: str) -> list[dict[str, str]]:
477+
"""Extract meaningful search terms and infer asset filter types from query."""
478+
words = query.lower().replace("-", " ").replace("_", " ").split()
479+
terms = [w for w in words if w not in _ASSET_STOPWORDS and len(w) > 1]
480+
481+
# Infer filter_type from keywords
482+
filter_type = None
483+
for w in words:
484+
if w in _KEYWORD_TO_FILTER_TYPE:
485+
filter_type = _KEYWORD_TO_FILTER_TYPE[w]
486+
break
487+
488+
# Build search patterns: each non-stopword term as a separate search
489+
searches = []
490+
for term in terms:
491+
if term in _ASSET_KEYWORDS:
492+
continue # Skip generic keywords like "shader" — they're too broad
493+
params: dict[str, str] = {"search_pattern": f"*{term}*"}
494+
if filter_type:
495+
params["filter_type"] = filter_type
496+
searches.append(params)
497+
498+
# If only keywords remain (e.g., "2D shader"), search by filter type alone
499+
if not searches and filter_type:
500+
searches.append({"filter_type": filter_type})
501+
502+
return searches
503+
504+
457505
async def _search_assets(ctx: Any, query: str) -> dict[str, Any] | None:
458506
"""Search Unity assets if ctx has a Unity connection. Returns None if unavailable."""
459507
try:
@@ -462,23 +510,43 @@ async def _search_assets(ctx: Any, query: str) -> dict[str, Any] | None:
462510
from transport.legacy.unity_connection import async_send_command_with_retry
463511

464512
unity_instance = await get_unity_instance_from_context(ctx)
465-
result = await send_with_unity_instance(
466-
async_send_command_with_retry,
467-
unity_instance,
468-
"manage_asset",
469-
{"action": "search", "path": ".", "search_pattern": f"*{query}*", "pageSize": 10},
470-
)
471-
if isinstance(result, dict) and result.get("success"):
472-
assets = result.get("data", {}).get("assets", [])
473-
if assets:
474-
return {
475-
"source": "project_assets",
476-
"found": True,
477-
"assets": [
478-
{"name": a.get("name", ""), "path": a.get("path", ""), "type": a.get("assetType", "")}
479-
for a in assets
480-
],
481-
}
513+
514+
search_terms = _build_asset_search_terms(query)
515+
if not search_terms:
516+
return None
517+
518+
# Run all search terms in parallel
519+
all_assets = []
520+
seen_paths: set[str] = set()
521+
522+
async def _do_search(params: dict) -> list[dict]:
523+
search_params: dict[str, Any] = {"action": "search", "path": ".", "pageSize": 10}
524+
search_params.update(params)
525+
result = await send_with_unity_instance(
526+
async_send_command_with_retry, unity_instance, "manage_asset", search_params,
527+
)
528+
if isinstance(result, dict) and result.get("success"):
529+
return result.get("data", {}).get("assets", [])
530+
return []
531+
532+
results = await asyncio.gather(*[_do_search(p) for p in search_terms], return_exceptions=True)
533+
534+
for result in results:
535+
if isinstance(result, list):
536+
for a in result:
537+
path = a.get("path", "")
538+
if path and path not in seen_paths:
539+
seen_paths.add(path)
540+
all_assets.append(
541+
{"name": a.get("name", ""), "path": path, "type": a.get("assetType", "")}
542+
)
543+
544+
if all_assets:
545+
return {
546+
"source": "project_assets",
547+
"found": True,
548+
"assets": all_assets[:15], # Cap to avoid huge payloads
549+
}
482550
except Exception:
483551
pass # No Unity connection or import failure — skip silently
484552
return None

Server/tests/test_unity_docs.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,3 +660,23 @@ def test_asset_keyword_detection():
660660
assert _should_search_assets("Physics.Raycast") is False
661661
assert _should_search_assets("NavMeshAgent") is False
662662
assert _should_search_assets("execution-order") is False
663+
664+
665+
def test_build_asset_search_terms():
666+
"""Extract meaningful search terms from query, infer filter types."""
667+
from services.tools.unity_docs import _build_asset_search_terms
668+
# "Mesh2D shader" → search for *mesh2d* with filter_type=Shader
669+
terms = _build_asset_search_terms("Mesh2D shader")
670+
assert len(terms) >= 1
671+
assert any("mesh2d" in t.get("search_pattern", "") for t in terms)
672+
assert any(t.get("filter_type") == "Shader" for t in terms)
673+
674+
# "MeshRenderer 2D lights" → search for *meshrenderer*, *lights* (2d triggers keyword)
675+
terms = _build_asset_search_terms("MeshRenderer 2D lights")
676+
assert len(terms) >= 1
677+
assert any("meshrenderer" in t.get("search_pattern", "") for t in terms)
678+
679+
# "Physics.Raycast" → no asset search terms (no asset keywords)
680+
# (This won't be called since _should_search_assets returns False, but test the function)
681+
terms = _build_asset_search_terms("Physics.Raycast")
682+
assert len(terms) >= 1 # Still extracts terms, just won't be triggered

0 commit comments

Comments
 (0)