hedit 0.7.6.dev2__tar.gz → 0.7.6.dev4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. {hedit-0.7.6.dev2/hedit.egg-info → hedit-0.7.6.dev4}/PKG-INFO +1 -1
  2. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4/hedit.egg-info}/PKG-INFO +1 -1
  3. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/pyproject.toml +1 -1
  4. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/agents/annotation_agent.py +34 -10
  5. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/api/main.py +13 -13
  6. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/api/models.py +2 -2
  7. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/cli/config.py +5 -5
  8. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/cli/local_executor.py +4 -5
  9. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/cli/main.py +1 -1
  10. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/scripts/process_feedback.py +1 -1
  11. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/utils/hed_comprehensive_guide.py +19 -9
  12. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/utils/litellm_llm.py +1 -1
  13. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/utils/openrouter_llm.py +2 -2
  14. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/version.py +1 -1
  15. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_annotation_agent.py +126 -0
  16. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_comprehensive_guide.py +13 -27
  17. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/LICENSE +0 -0
  18. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/PKG_README.md +0 -0
  19. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/README.md +0 -0
  20. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/hedit.egg-info/SOURCES.txt +0 -0
  21. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/hedit.egg-info/dependency_links.txt +0 -0
  22. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/hedit.egg-info/entry_points.txt +0 -0
  23. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/hedit.egg-info/requires.txt +0 -0
  24. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/hedit.egg-info/top_level.txt +0 -0
  25. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/setup.cfg +0 -0
  26. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/__init__.py +0 -0
  27. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/agents/__init__.py +0 -0
  28. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/agents/assessment_agent.py +0 -0
  29. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/agents/evaluation_agent.py +0 -0
  30. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/agents/feedback_summarizer.py +0 -0
  31. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/agents/feedback_triage_agent.py +0 -0
  32. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/agents/state.py +0 -0
  33. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/agents/validation_agent.py +0 -0
  34. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/agents/vision_agent.py +0 -0
  35. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/agents/workflow.py +0 -0
  36. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/api/__init__.py +0 -0
  37. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/api/security.py +0 -0
  38. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/cli/__init__.py +0 -0
  39. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/cli/api_executor.py +0 -0
  40. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/cli/client.py +0 -0
  41. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/cli/commands/__init__.py +0 -0
  42. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/cli/executor.py +0 -0
  43. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/cli/output.py +0 -0
  44. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/scripts/__init__.py +0 -0
  45. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/telemetry/__init__.py +0 -0
  46. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/telemetry/collector.py +0 -0
  47. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/telemetry/schema.py +0 -0
  48. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/telemetry/storage.py +0 -0
  49. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/utils/__init__.py +0 -0
  50. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/utils/error_remediation.py +0 -0
  51. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/utils/github_client.py +0 -0
  52. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/utils/hed_rules.py +0 -0
  53. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/utils/image_processing.py +0 -0
  54. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/utils/json_schema_loader.py +0 -0
  55. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/utils/schema_loader.py +0 -0
  56. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/validation/__init__.py +0 -0
  57. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/validation/hed_lsp.py +0 -0
  58. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/src/validation/hed_validator.py +0 -0
  59. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_api_endpoints.py +0 -0
  60. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_cli_client.py +0 -0
  61. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_cli_config.py +0 -0
  62. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_cli_integration.py +0 -0
  63. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_cli_main.py +0 -0
  64. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_error_remediation.py +0 -0
  65. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_feedback_integration.py +0 -0
  66. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_feedback_triage.py +0 -0
  67. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_github_client.py +0 -0
  68. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_hed_lsp.py +0 -0
  69. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_integration_openrouter.py +0 -0
  70. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_json_schema_loader.py +0 -0
  71. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_keyword_extraction.py +0 -0
  72. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_litellm_llm.py +0 -0
  73. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_no_extend_propagation.py +0 -0
  74. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_openrouter_llm.py +0 -0
  75. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_schema_loader.py +0 -0
  76. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_security.py +0 -0
  77. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_state.py +0 -0
  78. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_telemetry.py +0 -0
  79. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_validation.py +0 -0
  80. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_validation_agent.py +0 -0
  81. {hedit-0.7.6.dev2 → hedit-0.7.6.dev4}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hedit
3
- Version: 0.7.6.dev2
3
+ Version: 0.7.6.dev4
4
4
  Summary: Multi-agent system for HED annotation generation and validation
5
5
  Author-email: Annotation Garden Initiative <info@annotation.garden>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hedit
3
- Version: 0.7.6.dev2
3
+ Version: 0.7.6.dev4
4
4
  Summary: Multi-agent system for HED annotation generation and validation
5
5
  Author-email: Annotation Garden Initiative <info@annotation.garden>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hedit"
7
- version = "0.7.6.dev2"
7
+ version = "0.7.6.dev4"
8
8
  description = "Multi-agent system for HED annotation generation and validation"
9
9
  readme = "PKG_README.md"
10
10
  requires-python = ">=3.12"
@@ -12,7 +12,7 @@ from langchain_core.messages import HumanMessage, SystemMessage
12
12
 
13
13
  from src.agents.state import HedAnnotationState
14
14
  from src.utils import extract_text_content
15
- from src.utils.hed_comprehensive_guide import get_comprehensive_hed_guide
15
+ from src.utils.hed_comprehensive_guide import format_semantic_hints, get_comprehensive_hed_guide
16
16
  from src.utils.json_schema_loader import HedJsonSchemaLoader, load_latest_schema
17
17
 
18
18
  logger = logging.getLogger(__name__)
@@ -60,21 +60,23 @@ class AnnotationAgent:
60
60
  self,
61
61
  vocabulary: list[str],
62
62
  extendable_tags: list[str],
63
- semantic_hints: list[dict] | None = None,
64
63
  no_extend: bool = False,
65
64
  ) -> str:
66
65
  """Build the system prompt for the annotation agent.
67
66
 
67
+ The system prompt is static per schema version (vocabulary + rules)
68
+ to enable prompt caching across requests. Semantic hints are placed
69
+ in the user prompt instead.
70
+
68
71
  Args:
69
72
  vocabulary: List of valid short-form HED tags
70
73
  extendable_tags: Tags that allow extension
71
- semantic_hints: Optional semantic search results with relevant tags
72
74
  no_extend: If True, prohibit tag extensions
73
75
 
74
76
  Returns:
75
77
  Complete system prompt with all HED rules
76
78
  """
77
- return get_comprehensive_hed_guide(vocabulary, extendable_tags, semantic_hints, no_extend)
79
+ return get_comprehensive_hed_guide(vocabulary, extendable_tags, no_extend)
78
80
 
79
81
  def _format_tag_suggestions(self, tag_suggestions: dict[str, list[str]]) -> str:
80
82
  """Format tag suggestions into a clear instruction block.
@@ -98,24 +100,46 @@ class AnnotationAgent:
98
100
  )
99
101
  return "\n".join(lines)
100
102
 
103
+ def _format_semantic_hints(self, semantic_hints: list[dict] | None) -> str:
104
+ """Format semantic hints for inclusion in the user prompt.
105
+
106
+ Args:
107
+ semantic_hints: List of hint dicts with tag, score, source keys
108
+
109
+ Returns:
110
+ Formatted hints section, or empty string if no hints
111
+ """
112
+ if not semantic_hints:
113
+ return ""
114
+
115
+ logger.debug("Including %d semantic hints in user prompt", len(semantic_hints))
116
+ return "\n" + format_semantic_hints(semantic_hints)
117
+
101
118
  def _build_user_prompt(
102
119
  self,
103
120
  description: str,
104
121
  validation_errors: list[str] | None = None,
105
122
  tag_suggestions: dict[str, list[str]] | None = None,
106
123
  previous_annotation: str | None = None,
124
+ semantic_hints: list[dict] | None = None,
107
125
  ) -> str:
108
126
  """Build the user prompt for annotation.
109
127
 
128
+ Semantic hints are included here (not in system prompt) so the
129
+ system prompt stays static and cacheable across requests.
130
+
110
131
  Args:
111
132
  description: Natural language event description
112
133
  validation_errors: Previous validation errors (if retrying)
113
134
  tag_suggestions: LSP-suggested valid tags for invalid tags
114
135
  previous_annotation: The previous annotation attempt (for targeted correction)
136
+ semantic_hints: Optional semantic search hints for relevant tags
115
137
 
116
138
  Returns:
117
139
  User prompt string
118
140
  """
141
+ hints_str = self._format_semantic_hints(semantic_hints)
142
+
119
143
  if validation_errors:
120
144
  errors_str = "\n".join(f"- {error}" for error in validation_errors)
121
145
  suggestions_str = self._format_tag_suggestions(tag_suggestions or {})
@@ -133,12 +157,12 @@ class AnnotationAgent:
133
157
 
134
158
  Fix these errors and generate a corrected HED annotation for:
135
159
  {description}
136
-
160
+ {hints_str}
137
161
  {replacement_note}CRITICAL: Output ONLY the raw HED annotation string."""
138
162
 
139
163
  return f"""Generate a HED annotation for this event description:
140
164
  {description}
141
-
165
+ {hints_str}
142
166
  CRITICAL: Output ONLY the raw HED annotation string."""
143
167
 
144
168
  async def annotate(self, state: HedAnnotationState) -> dict:
@@ -170,17 +194,16 @@ CRITICAL: Output ONLY the raw HED annotation string."""
170
194
  # Use empty list - LLM will still generate valid annotations
171
195
  extendable_tags = []
172
196
 
173
- # Build prompts with complete HED rules (including semantic hints if available)
174
- semantic_hints = state.get("semantic_hints", [])
197
+ # Build system prompt with HED rules (static per schema version for caching)
175
198
  no_extend = state.get("no_extend", False)
176
199
  system_prompt = self._build_system_prompt(
177
200
  vocabulary,
178
201
  extendable_tags,
179
- semantic_hints if semantic_hints else None,
180
202
  no_extend,
181
203
  )
182
204
 
183
- # Build user prompt with any feedback (use augmented errors with remediation for LLM)
205
+ # Build user prompt with feedback and semantic hints
206
+ semantic_hints = state.get("semantic_hints", [])
184
207
  feedbacks = []
185
208
  if state.get("validation_errors_augmented"):
186
209
  feedbacks.extend(state["validation_errors_augmented"])
@@ -200,6 +223,7 @@ CRITICAL: Output ONLY the raw HED annotation string."""
200
223
  feedbacks or None,
201
224
  tag_suggestions or None,
202
225
  previous_annotation,
226
+ semantic_hints if semantic_hints else None,
203
227
  )
204
228
 
205
229
  # Generate annotation
@@ -113,8 +113,8 @@ def create_openrouter_workflow(
113
113
  # Apply defaults from environment
114
114
  default_annotation_model = os.getenv("ANNOTATION_MODEL", "anthropic/claude-haiku-4.5")
115
115
  default_annotation_provider = os.getenv("ANNOTATION_PROVIDER", "anthropic")
116
- default_eval_model = os.getenv("EVALUATION_MODEL", "openai/gpt-oss-120b")
117
- default_eval_provider = os.getenv("EVALUATION_PROVIDER", "groq")
116
+ default_eval_model = os.getenv("EVALUATION_MODEL", "qwen/qwen3.5-397b-a17b")
117
+ default_eval_provider = os.getenv("EVALUATION_PROVIDER")
118
118
 
119
119
  # Resolve final values: parameter > env var > default
120
120
  actual_annotation_model = get_model_name(annotation_model or default_annotation_model)
@@ -240,8 +240,8 @@ def create_byok_vision_agent(
240
240
  Configured VisionAgent using the user's key and model settings
241
241
  """
242
242
  # Use user-provided settings or fall back to server defaults
243
- default_vision_model = os.getenv("VISION_MODEL", "qwen/qwen3-vl-30b-a3b-instruct")
244
- default_vision_provider = os.getenv("VISION_PROVIDER", "deepinfra/fp8")
243
+ default_vision_model = os.getenv("VISION_MODEL", "qwen/qwen3-vl-32b-instruct")
244
+ default_vision_provider = os.getenv("VISION_PROVIDER", "novita")
245
245
 
246
246
  actual_model = vision_model if vision_model else default_vision_model
247
247
  actual_temperature = temperature if temperature is not None else 0.3
@@ -354,7 +354,7 @@ async def lifespan(app: FastAPI):
354
354
  # Log configuration (env vars are read by create_openrouter_workflow)
355
355
  print("Using OpenRouter with models:")
356
356
  print(f" Annotation: {os.getenv('ANNOTATION_MODEL', 'anthropic/claude-haiku-4.5')}")
357
- print(f" Evaluation: {os.getenv('EVALUATION_MODEL', 'qwen/qwen3-235b-a22b-2507')}")
357
+ print(f" Evaluation: {os.getenv('EVALUATION_MODEL', 'qwen/qwen3.5-397b-a17b')}")
358
358
  print(f" Provider (annotation): {os.getenv('ANNOTATION_PROVIDER', 'anthropic')}")
359
359
  print(f" Provider (eval): {os.getenv('EVALUATION_PROVIDER', '') or '(auto-routed)'}")
360
360
 
@@ -395,8 +395,8 @@ async def lifespan(app: FastAPI):
395
395
 
396
396
  # Initialize vision agent (only for OpenRouter)
397
397
  if llm_provider == "openrouter":
398
- vision_model = os.getenv("VISION_MODEL", "qwen/qwen3-vl-30b-a3b-instruct")
399
- vision_provider = os.getenv("VISION_PROVIDER", "deepinfra/fp8")
398
+ vision_model = os.getenv("VISION_MODEL", "qwen/qwen3-vl-32b-instruct")
399
+ vision_provider = os.getenv("VISION_PROVIDER", "novita")
400
400
 
401
401
  print(f"Initializing vision model: {vision_model} (provider: {vision_provider})")
402
402
 
@@ -666,7 +666,7 @@ async def annotate(
666
666
  model_name = (
667
667
  request.model
668
668
  or req.headers.get("x-openrouter-model")
669
- or os.getenv("ANNOTATION_MODEL", "openai/gpt-oss-120b")
669
+ or os.getenv("ANNOTATION_MODEL", "anthropic/claude-haiku-4.5")
670
670
  )
671
671
  temperature = request.temperature
672
672
  if temperature is None:
@@ -815,7 +815,7 @@ async def annotate_from_image(
815
815
  validator_path=_byok_config.get("validator_path"),
816
816
  use_js_validator=_byok_config.get("use_js_validator", True),
817
817
  )
818
- # Note: Vision agent uses its own provider (deepinfra/fp8 for qwen-vl)
818
+ # Note: Vision agent uses its own provider (novita for qwen-vl)
819
819
  # Only pass provider_override to vision if a custom vision_model was specified
820
820
  vision_provider = provider_override if vision_model_override else None
821
821
  active_vision_agent = create_byok_vision_agent(
@@ -877,7 +877,7 @@ async def annotate_from_image(
877
877
  model_name = (
878
878
  request.model
879
879
  or req.headers.get("x-openrouter-model")
880
- or os.getenv("ANNOTATION_MODEL", "openai/gpt-oss-120b")
880
+ or os.getenv("ANNOTATION_MODEL", "anthropic/claude-haiku-4.5")
881
881
  )
882
882
  temperature = request.temperature
883
883
  if temperature is None:
@@ -967,7 +967,7 @@ async def _collect_stream_telemetry(
967
967
  model_name = (
968
968
  request.model
969
969
  or req.headers.get("x-openrouter-model")
970
- or os.getenv("ANNOTATION_MODEL", "openai/gpt-oss-120b")
970
+ or os.getenv("ANNOTATION_MODEL", "anthropic/claude-haiku-4.5")
971
971
  )
972
972
  temperature = request.temperature
973
973
  if temperature is None:
@@ -1386,7 +1386,7 @@ async def annotate_from_image_stream(
1386
1386
  validator_path=_byok_config.get("validator_path"),
1387
1387
  use_js_validator=_byok_config.get("use_js_validator", True),
1388
1388
  )
1389
- # Note: Vision agent uses its own provider (deepinfra/fp8 for qwen-vl)
1389
+ # Note: Vision agent uses its own provider (novita for qwen-vl)
1390
1390
  # Only pass provider_override to vision if a custom vision_model was specified
1391
1391
  vision_provider = provider_override if vision_model_override else None
1392
1392
  active_vision_agent = create_byok_vision_agent(
@@ -1785,7 +1785,7 @@ async def submit_feedback(request: FeedbackRequest) -> FeedbackResponse:
1785
1785
  )
1786
1786
 
1787
1787
  # Create LLM for triage
1788
- model = os.getenv("ANNOTATION_MODEL", "openai/gpt-oss-120b")
1788
+ model = os.getenv("ANNOTATION_MODEL", "anthropic/claude-haiku-4.5")
1789
1789
  provider = os.getenv("LLM_PROVIDER_PREFERENCE", "")
1790
1790
  llm = create_openrouter_llm(
1791
1791
  model=model,
@@ -41,7 +41,7 @@ class AnnotationRequest(BaseModel):
41
41
  model: str | None = Field(
42
42
  default=None,
43
43
  description="Override model for annotation (BYOK mode only, e.g., 'openai/gpt-4o')",
44
- examples=["openai/gpt-4o", "anthropic/claude-3.5-sonnet", "qwen/qwen3-235b-a22b-2507"],
44
+ examples=["anthropic/claude-haiku-4.5", "qwen/qwen3.5-122b-a10b", "qwen/qwen3.5-397b-a17b"],
45
45
  )
46
46
  provider: str | None = Field(
47
47
  default=None,
@@ -177,7 +177,7 @@ class ImageAnnotationRequest(BaseModel):
177
177
  vision_model: str | None = Field(
178
178
  default=None,
179
179
  description="Override vision model for image description (BYOK mode only)",
180
- examples=["qwen/qwen3-vl-30b-a3b-instruct", "openai/gpt-4o"],
180
+ examples=["qwen/qwen3-vl-32b-instruct", "qwen/qwen3-vl-235b-a22b-instruct"],
181
181
  )
182
182
  provider: str | None = Field(
183
183
  default=None,
@@ -41,13 +41,13 @@ DEFAULT_DEV_API_URL = "https://api.annotation.garden/hedit-dev"
41
41
  DEFAULT_MODEL = "anthropic/claude-haiku-4.5"
42
42
  DEFAULT_PROVIDER = "anthropic"
43
43
 
44
- # Evaluation model: Qwen3-235B (auto-routed by OpenRouter)
45
- DEFAULT_EVAL_MODEL = "qwen/qwen3-235b-a22b-2507"
44
+ # Evaluation model: Qwen3.5-397B MoE (most capable Qwen, high throughput)
45
+ DEFAULT_EVAL_MODEL = "qwen/qwen3.5-397b-a17b"
46
46
  DEFAULT_EVAL_PROVIDER = None
47
47
 
48
- # Vision model: Qwen3-VL for image descriptions
49
- DEFAULT_VISION_MODEL = "qwen/qwen3-vl-30b-a3b-instruct"
50
- DEFAULT_VISION_PROVIDER = "deepinfra/fp8"
48
+ # Vision model: Qwen3-VL-32B for image descriptions
49
+ DEFAULT_VISION_MODEL = "qwen/qwen3-vl-32b-instruct"
50
+ DEFAULT_VISION_PROVIDER = None
51
51
 
52
52
 
53
53
  class CredentialsConfig(BaseModel):
@@ -77,11 +77,10 @@ class LocalExecutionBackend(ExecutionBackend):
77
77
 
78
78
  Args:
79
79
  api_key: OpenRouter API key (required for LLM operations, optional for health/validate)
80
- model: Model for text annotation (default: openai/gpt-oss-120b)
81
- eval_model: Model for evaluation/assessment agents (default: same as model).
82
- Use a consistent model like qwen/qwen3-235b-a22b for fair benchmarking.
83
- eval_provider: Provider for evaluation model (e.g., Cerebras for qwen models)
84
- vision_model: Model for image annotation (default: qwen/qwen3-vl-30b-a3b-instruct)
80
+ model: Model for text annotation (default: anthropic/claude-haiku-4.5)
81
+ eval_model: Model for evaluation/assessment agents (default: qwen/qwen3.5-397b-a17b)
82
+ eval_provider: Provider for evaluation model (None = OpenRouter auto-routes)
83
+ vision_model: Model for image annotation (default: qwen/qwen3-vl-32b-instruct)
85
84
  provider: Provider preference (cleared if custom model specified)
86
85
  temperature: LLM temperature (0.0-1.0)
87
86
  schema_dir: Optional directory with JSON schemas (None = fetch from GitHub)
@@ -102,7 +102,7 @@ ModelOption = Annotated[
102
102
  typer.Option(
103
103
  "--model",
104
104
  "-m",
105
- help="Model to use (e.g., openai/gpt-oss-120b, gpt-4o-mini)",
105
+ help="Model to use (e.g., anthropic/claude-haiku-4.5, qwen/qwen3.5-122b-a10b)",
106
106
  ),
107
107
  ]
108
108
 
@@ -56,7 +56,7 @@ async def process_feedback_file(
56
56
  sys.exit(1)
57
57
 
58
58
  # Get model configuration from environment
59
- model = os.getenv("ANNOTATION_MODEL", "openai/gpt-oss-120b")
59
+ model = os.getenv("ANNOTATION_MODEL", "anthropic/claude-haiku-4.5")
60
60
  provider = os.getenv("LLM_PROVIDER_PREFERENCE", "")
61
61
 
62
62
  # Create LLM
@@ -10,8 +10,8 @@ assembles them into the full system prompt.
10
10
  """
11
11
 
12
12
 
13
- def _format_semantic_hints(hints: list[dict]) -> str:
14
- """Format semantic hints for inclusion in the guide.
13
+ def format_semantic_hints(hints: list[dict]) -> str:
14
+ """Format semantic hints for inclusion in the user prompt.
15
15
 
16
16
  Args:
17
17
  hints: List of semantic search results, each with:
@@ -21,7 +21,7 @@ def _format_semantic_hints(hints: list[dict]) -> str:
21
21
  - prefix: Optional library prefix (e.g., "sc:")
22
22
 
23
23
  Returns:
24
- Formatted hints section for the guide
24
+ Formatted hints section for the user prompt
25
25
  """
26
26
  if not hints:
27
27
  return ""
@@ -33,6 +33,8 @@ def _format_semantic_hints(hints: list[dict]) -> str:
33
33
 
34
34
  for hint in hints:
35
35
  tag = hint.get("tag", "")
36
+ if not tag:
37
+ continue
36
38
  prefix = hint.get("prefix", "")
37
39
  score = hint.get("score", 0)
38
40
  full_tag = f"{prefix}{tag}" if prefix else tag
@@ -45,7 +47,7 @@ def _format_semantic_hints(hints: list[dict]) -> str:
45
47
  low_conf.append(full_tag)
46
48
 
47
49
  lines = [
48
- "## POTENTIALLY RELEVANT TAGS",
50
+ "## SEMANTIC HINTS",
49
51
  "",
50
52
  "Based on your description, these schema tags may be relevant.",
51
53
  "Note: this list may contain false positives - use your judgment.",
@@ -974,7 +976,6 @@ Just output the raw HED annotation string directly.
974
976
  def get_comprehensive_hed_guide(
975
977
  vocabulary_sample: list[str],
976
978
  extendable_tags: list[str],
977
- semantic_hints: list[dict] | None = None,
978
979
  no_extend: bool = False,
979
980
  ) -> str:
980
981
  """Generate comprehensive HED annotation guide.
@@ -983,11 +984,13 @@ def get_comprehensive_hed_guide(
983
984
  annotation agent. The guide includes vocabulary constraints, semantic
984
985
  rules, correction workflows, and output format instructions.
985
986
 
987
+ Note: Semantic hints are NOT included here to keep the system prompt
988
+ static across requests, enabling prompt caching. Hints are passed
989
+ in the user prompt instead.
990
+
986
991
  Args:
987
992
  vocabulary_sample: Full list of valid HED tags (complete vocabulary)
988
993
  extendable_tags: Tags that allow extension
989
- semantic_hints: Optional list of semantically relevant tags from search
990
- Each dict has: tag, score, source, prefix (optional)
991
994
  no_extend: If True, add strict instructions to prohibit tag extensions
992
995
 
993
996
  Returns:
@@ -997,16 +1000,23 @@ def get_comprehensive_hed_guide(
997
1000
  extend_str = ", ".join(extendable_tags) if not no_extend else "(Extensions disabled)"
998
1001
 
999
1002
  # Format optional sections
1000
- hints_section = _format_semantic_hints(semantic_hints) if semantic_hints else ""
1001
1003
  no_extend_warning = _build_no_extend_warning() if no_extend else ""
1002
1004
 
1003
1005
  # Assemble guide from modular sections
1006
+ # Note: semantic hints are placed in the user prompt for cache efficiency
1004
1007
  sections = [
1005
1008
  "# HED ANNOTATION GUIDE\n",
1006
1009
  no_extend_warning,
1007
1010
  _build_vocabulary_check_section(),
1008
1011
  _build_correction_workflow_section(),
1009
- hints_section,
1012
+ (
1013
+ "## SEMANTIC HINTS\n\n"
1014
+ "The user message may include a SEMANTIC HINTS section with "
1015
+ "potentially relevant tags from schema search. If present, use "
1016
+ "these as guidance for tag selection, but verify each against "
1017
+ "the vocabulary. If no hints section is present, proceed without them.\n\n"
1018
+ "---\n\n"
1019
+ ),
1010
1020
  _build_semantic_rules_section(),
1011
1021
  _build_relation_tags_section(),
1012
1022
  _build_event_agent_section(),
@@ -29,7 +29,7 @@ from langchain_core.messages import BaseMessage
29
29
 
30
30
 
31
31
  def create_litellm_openrouter(
32
- model: str = "openai/gpt-oss-120b",
32
+ model: str = "anthropic/claude-haiku-4.5",
33
33
  api_key: str | None = None,
34
34
  temperature: float = 0.1,
35
35
  max_tokens: int | None = None,
@@ -13,7 +13,7 @@ from langchain_core.messages import BaseMessage
13
13
 
14
14
 
15
15
  def create_openrouter_llm(
16
- model: str = "openai/gpt-oss-120b",
16
+ model: str = "anthropic/claude-haiku-4.5",
17
17
  api_key: str | None = None,
18
18
  temperature: float = 0.1,
19
19
  max_tokens: int | None = None,
@@ -28,7 +28,7 @@ def create_openrouter_llm(
28
28
  to include cache_control markers for 90% cost reduction on cache hits.
29
29
 
30
30
  Args:
31
- model: Model identifier (e.g., "openai/gpt-oss-120b", "anthropic/claude-haiku-4.5")
31
+ model: Model identifier (e.g., "anthropic/claude-haiku-4.5", "qwen/qwen3.5-397b-a17b")
32
32
  api_key: OpenRouter API key (defaults to OPENROUTER_API_KEY env var)
33
33
  temperature: Sampling temperature (0.0-1.0)
34
34
  max_tokens: Maximum tokens to generate
@@ -1,6 +1,6 @@
1
1
  """Version information for HEDit."""
2
2
 
3
- __version__ = "0.7.6.dev2"
3
+ __version__ = "0.7.6.dev4"
4
4
  __version_info__ = (0, 7, 6, "dev")
5
5
 
6
6
 
@@ -193,6 +193,132 @@ class TestBuildUserPrompt:
193
193
 
194
194
  assert "Previous annotation:" not in result
195
195
 
196
+ def test_first_pass_with_semantic_hints(self):
197
+ """Semantic hints should appear in the user prompt on first pass."""
198
+ agent = self._make_agent()
199
+ result = agent._build_user_prompt(
200
+ "A dog chasing a cat",
201
+ semantic_hints=[
202
+ {"tag": "Animal-agent", "score": 0.9, "source": "hed-lsp"},
203
+ {"tag": "Chase", "score": 0.7, "source": "hed-lsp"},
204
+ ],
205
+ )
206
+
207
+ assert "SEMANTIC HINTS" in result
208
+ assert "Animal-agent" in result
209
+ assert "Chase" in result
210
+ assert "A dog chasing a cat" in result
211
+
212
+ def test_correction_pass_with_semantic_hints(self):
213
+ """Semantic hints should also appear in correction prompts."""
214
+ agent = self._make_agent()
215
+ result = agent._build_user_prompt(
216
+ "A dog chasing a cat",
217
+ validation_errors=["[TAG_INVALID] 'Chase' is not valid"],
218
+ semantic_hints=[
219
+ {"tag": "Animal-agent", "score": 0.9, "source": "hed-lsp"},
220
+ ],
221
+ )
222
+
223
+ assert "SEMANTIC HINTS" in result
224
+ assert "Animal-agent" in result
225
+ assert "TAG_INVALID" in result
226
+
227
+ def test_no_hints_no_hints_section(self):
228
+ """No hints should not add a hints section to the user prompt."""
229
+ agent = self._make_agent()
230
+ result = agent._build_user_prompt("A red circle")
231
+
232
+ assert "SEMANTIC HINTS" not in result
233
+
234
+ def test_empty_hints_no_hints_section(self):
235
+ """Empty hints list should not add a hints section."""
236
+ agent = self._make_agent()
237
+ result = agent._build_user_prompt("A red circle", semantic_hints=[])
238
+
239
+ assert "SEMANTIC HINTS" not in result
240
+
241
+
242
+ class TestFormatSemanticHints:
243
+ """Tests for _format_semantic_hints method on AnnotationAgent."""
244
+
245
+ def _make_agent(self):
246
+ agent = object.__new__(AnnotationAgent)
247
+ return agent
248
+
249
+ def test_none_returns_empty(self):
250
+ """None input should return empty string."""
251
+ agent = self._make_agent()
252
+ assert agent._format_semantic_hints(None) == ""
253
+
254
+ def test_empty_list_returns_empty(self):
255
+ """Empty list should return empty string."""
256
+ agent = self._make_agent()
257
+ assert agent._format_semantic_hints([]) == ""
258
+
259
+ def test_valid_hints_returns_content(self):
260
+ """Valid hints should produce formatted output."""
261
+ agent = self._make_agent()
262
+ result = agent._format_semantic_hints(
263
+ [
264
+ {"tag": "Red", "score": 0.9, "source": "hed-lsp"},
265
+ ]
266
+ )
267
+
268
+ assert "SEMANTIC HINTS" in result
269
+ assert "Red" in result
270
+
271
+ def test_confidence_bucketing(self):
272
+ """Hints should be categorized by confidence level."""
273
+ agent = self._make_agent()
274
+ result = agent._format_semantic_hints(
275
+ [
276
+ {"tag": "HighTag", "score": 0.95, "source": "hed-lsp"},
277
+ {"tag": "MedTag", "score": 0.6, "source": "hed-lsp"},
278
+ {"tag": "LowTag", "score": 0.3, "source": "hed-lsp"},
279
+ ]
280
+ )
281
+
282
+ assert "High confidence" in result
283
+ assert "HighTag" in result
284
+ assert "Medium confidence" in result
285
+ assert "MedTag" in result
286
+ assert "Lower confidence" in result
287
+ assert "LowTag" in result
288
+
289
+ def test_skips_empty_tags(self):
290
+ """Hints with empty tag should be skipped."""
291
+ agent = self._make_agent()
292
+ result = agent._format_semantic_hints(
293
+ [
294
+ {"tag": "", "score": 0.9, "source": "hed-lsp"},
295
+ {"tag": "ValidTag", "score": 0.8, "source": "hed-lsp"},
296
+ ]
297
+ )
298
+
299
+ assert "ValidTag" in result
300
+
301
+
302
+ class TestSystemPromptCaching:
303
+ """Tests that system prompt is static (no dynamic content) for caching."""
304
+
305
+ def test_system_prompt_has_hints_pointer_not_content(self):
306
+ """System prompt should reference hints but not contain actual hint data."""
307
+ from src.utils.hed_comprehensive_guide import get_comprehensive_hed_guide
308
+
309
+ guide = get_comprehensive_hed_guide(
310
+ vocabulary_sample=["Red", "Circle"],
311
+ extendable_tags=["Animal"],
312
+ )
313
+
314
+ # Should have the pointer section
315
+ assert "## SEMANTIC HINTS" in guide
316
+ assert "may include" in guide
317
+ # Should NOT contain dynamic hint content
318
+ assert "High confidence" not in guide
319
+ assert "Medium confidence" not in guide
320
+ assert "Lower confidence" not in guide
321
+
196
322
 
197
323
  class TestPromptSections:
198
324
  """Tests for prompt structure and sections."""
@@ -45,38 +45,24 @@ class TestComprehensiveGuide:
45
45
  # Should show extensions as disabled
46
46
  assert "(Extensions disabled)" in guide
47
47
 
48
- def test_guide_with_semantic_hints(self):
49
- """Test guide generation with semantic hints."""
48
+ def test_guide_has_semantic_hints_pointer(self):
49
+ """Test guide includes a pointer to check user message for semantic hints."""
50
50
  vocabulary = ["Event", "Reward", "Animal-agent"]
51
51
  extendable_tags = ["Label"]
52
- semantic_hints = [
53
- {"tag": "Reward", "prefix": "", "score": 0.95, "source": "keyword"},
54
- {"tag": "Animal-agent", "prefix": "", "score": 0.85, "source": "embedding"},
55
- ]
56
-
57
- guide = get_comprehensive_hed_guide(
58
- vocabulary, extendable_tags, semantic_hints=semantic_hints
59
- )
60
-
61
- assert "POTENTIALLY RELEVANT TAGS" in guide
62
- assert "Reward" in guide
63
- assert "Animal-agent" in guide
64
- # Check confidence indicators
65
- assert "high" in guide.lower() or "0.95" in guide
66
-
67
- def test_guide_with_semantic_hints_and_no_extend(self):
68
- """Test guide with both semantic hints and no_extend."""
52
+
53
+ guide = get_comprehensive_hed_guide(vocabulary, extendable_tags)
54
+
55
+ # System prompt should point to user message for hints (not contain them)
56
+ assert "SEMANTIC HINTS" in guide
57
+ assert "user message" in guide.lower()
58
+
59
+ def test_guide_no_extend_with_hints_pointer(self):
60
+ """Test guide with no_extend has both hints pointer and extension warning."""
69
61
  vocabulary = ["Event", "Visual-presentation"]
70
62
  extendable_tags = ["Label"]
71
- semantic_hints = [
72
- {"tag": "Visual-presentation", "prefix": "", "score": 0.9, "source": "keyword"},
73
- ]
74
63
 
75
- guide = get_comprehensive_hed_guide(
76
- vocabulary, extendable_tags, semantic_hints=semantic_hints, no_extend=True
77
- )
64
+ guide = get_comprehensive_hed_guide(vocabulary, extendable_tags, no_extend=True)
78
65
 
79
- # Should have both features
80
- assert "POTENTIALLY RELEVANT TAGS" in guide
66
+ assert "SEMANTIC HINTS" in guide
81
67
  assert "EXTENSIONS STRICTLY PROHIBITED" in guide
82
68
  assert "(Extensions disabled)" in guide
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes