agnt5 0.1.0__cp39-abi3-macosx_11_0_arm64.whl

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 (49) hide show
  1. agnt5/__init__.py +307 -0
  2. agnt5/__pycache__/__init__.cpython-311.pyc +0 -0
  3. agnt5/__pycache__/agent.cpython-311.pyc +0 -0
  4. agnt5/__pycache__/context.cpython-311.pyc +0 -0
  5. agnt5/__pycache__/durable.cpython-311.pyc +0 -0
  6. agnt5/__pycache__/extraction.cpython-311.pyc +0 -0
  7. agnt5/__pycache__/memory.cpython-311.pyc +0 -0
  8. agnt5/__pycache__/reflection.cpython-311.pyc +0 -0
  9. agnt5/__pycache__/runtime.cpython-311.pyc +0 -0
  10. agnt5/__pycache__/task.cpython-311.pyc +0 -0
  11. agnt5/__pycache__/tool.cpython-311.pyc +0 -0
  12. agnt5/__pycache__/tracing.cpython-311.pyc +0 -0
  13. agnt5/__pycache__/types.cpython-311.pyc +0 -0
  14. agnt5/__pycache__/workflow.cpython-311.pyc +0 -0
  15. agnt5/_core.abi3.so +0 -0
  16. agnt5/agent.py +1086 -0
  17. agnt5/context.py +406 -0
  18. agnt5/durable.py +1050 -0
  19. agnt5/extraction.py +410 -0
  20. agnt5/llm/__init__.py +179 -0
  21. agnt5/llm/__pycache__/__init__.cpython-311.pyc +0 -0
  22. agnt5/llm/__pycache__/anthropic.cpython-311.pyc +0 -0
  23. agnt5/llm/__pycache__/azure.cpython-311.pyc +0 -0
  24. agnt5/llm/__pycache__/base.cpython-311.pyc +0 -0
  25. agnt5/llm/__pycache__/google.cpython-311.pyc +0 -0
  26. agnt5/llm/__pycache__/mistral.cpython-311.pyc +0 -0
  27. agnt5/llm/__pycache__/openai.cpython-311.pyc +0 -0
  28. agnt5/llm/__pycache__/together.cpython-311.pyc +0 -0
  29. agnt5/llm/anthropic.py +319 -0
  30. agnt5/llm/azure.py +348 -0
  31. agnt5/llm/base.py +315 -0
  32. agnt5/llm/google.py +373 -0
  33. agnt5/llm/mistral.py +330 -0
  34. agnt5/llm/model_registry.py +467 -0
  35. agnt5/llm/models.json +227 -0
  36. agnt5/llm/openai.py +334 -0
  37. agnt5/llm/together.py +377 -0
  38. agnt5/memory.py +746 -0
  39. agnt5/reflection.py +514 -0
  40. agnt5/runtime.py +699 -0
  41. agnt5/task.py +476 -0
  42. agnt5/testing.py +451 -0
  43. agnt5/tool.py +516 -0
  44. agnt5/tracing.py +624 -0
  45. agnt5/types.py +210 -0
  46. agnt5/workflow.py +897 -0
  47. agnt5-0.1.0.dist-info/METADATA +93 -0
  48. agnt5-0.1.0.dist-info/RECORD +49 -0
  49. agnt5-0.1.0.dist-info/WHEEL +4 -0
agnt5/extraction.py ADDED
@@ -0,0 +1,410 @@
1
+ """
2
+ Data extraction utilities for AGNT5 SDK.
3
+
4
+ Provides specialized tools for extracting structured data from text,
5
+ particularly JSON data extraction with validation and error handling.
6
+ """
7
+
8
+ import json
9
+ import re
10
+ from typing import Any, Dict, List, Optional, Type, Union
11
+ from dataclasses import dataclass
12
+ from enum import Enum
13
+
14
+ from .task import task, json_extraction_task, OutputFormat, TaskResult
15
+ from .types import Message, MessageRole
16
+
17
+
18
+ class ExtractionStrategy(Enum):
19
+ """Different strategies for data extraction."""
20
+ REGEX = "regex"
21
+ LLM_GUIDED = "llm_guided"
22
+ TEMPLATE_BASED = "template_based"
23
+ HYBRID = "hybrid"
24
+
25
+
26
+ @dataclass
27
+ class ExtractionPattern:
28
+ """Pattern for data extraction."""
29
+ name: str
30
+ pattern: str
31
+ description: Optional[str] = None
32
+ required: bool = True
33
+ data_type: Type = str
34
+
35
+
36
+ class JSONExtractor:
37
+ """Utility class for JSON data extraction."""
38
+
39
+ def __init__(self):
40
+ self.common_patterns = {
41
+ "json_block": r'```json\s*(\{.*?\})\s*```',
42
+ "json_object": r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}',
43
+ "json_array": r'\[[^\[\]]*(?:\[[^\[\]]*\][^\[\]]*)*\]',
44
+ "code_block": r'```(?:json)?\s*(\{.*?\}|\[.*?\])\s*```',
45
+ }
46
+
47
+ def extract_json_from_text(self, text: str, strategy: ExtractionStrategy = ExtractionStrategy.HYBRID) -> List[Dict[str, Any]]:
48
+ """
49
+ Extract JSON objects from text using various strategies.
50
+
51
+ Args:
52
+ text: Input text to extract JSON from
53
+ strategy: Extraction strategy to use
54
+
55
+ Returns:
56
+ List of extracted JSON objects
57
+ """
58
+ if strategy == ExtractionStrategy.REGEX:
59
+ return self._extract_with_regex(text)
60
+ elif strategy == ExtractionStrategy.TEMPLATE_BASED:
61
+ return self._extract_with_templates(text)
62
+ elif strategy == ExtractionStrategy.HYBRID:
63
+ return self._extract_hybrid(text)
64
+ else:
65
+ return self._extract_with_regex(text) # Default fallback
66
+
67
+ def _extract_with_regex(self, text: str) -> List[Dict[str, Any]]:
68
+ """Extract JSON using regex patterns."""
69
+ results = []
70
+
71
+ # Try different patterns in order of specificity
72
+ patterns = [
73
+ self.common_patterns["json_block"],
74
+ self.common_patterns["code_block"],
75
+ self.common_patterns["json_object"],
76
+ self.common_patterns["json_array"],
77
+ ]
78
+
79
+ for pattern in patterns:
80
+ matches = re.finditer(pattern, text, re.DOTALL | re.IGNORECASE)
81
+ for match in matches:
82
+ json_text = match.group(1) if match.groups() else match.group(0)
83
+ try:
84
+ parsed = json.loads(json_text)
85
+ if isinstance(parsed, dict):
86
+ results.append(parsed)
87
+ elif isinstance(parsed, list):
88
+ # If it's a list of objects, add each one
89
+ for item in parsed:
90
+ if isinstance(item, dict):
91
+ results.append(item)
92
+ except json.JSONDecodeError:
93
+ continue
94
+
95
+ return results
96
+
97
+ def _extract_with_templates(self, text: str) -> List[Dict[str, Any]]:
98
+ """Extract JSON using common templates and structures."""
99
+ results = []
100
+
101
+ # Look for common JSON-like structures
102
+ # Pattern: key: value pairs
103
+ key_value_pattern = r'(\w+):\s*(["\'].*?["\']|\d+(?:\.\d+)?|true|false|null)'
104
+ matches = re.findall(key_value_pattern, text)
105
+
106
+ if matches:
107
+ # Build a JSON object from key-value pairs
108
+ obj = {}
109
+ for key, value in matches:
110
+ try:
111
+ # Try to parse the value as JSON
112
+ parsed_value = json.loads(value)
113
+ obj[key] = parsed_value
114
+ except json.JSONDecodeError:
115
+ # Keep as string if not valid JSON
116
+ obj[key] = value.strip('"\'')
117
+
118
+ if obj:
119
+ results.append(obj)
120
+
121
+ return results
122
+
123
+ def _extract_hybrid(self, text: str) -> List[Dict[str, Any]]:
124
+ """Combine multiple extraction strategies."""
125
+ results = []
126
+
127
+ # First try regex-based extraction
128
+ regex_results = self._extract_with_regex(text)
129
+ results.extend(regex_results)
130
+
131
+ # If no results, try template-based
132
+ if not results:
133
+ template_results = self._extract_with_templates(text)
134
+ results.extend(template_results)
135
+
136
+ # Remove duplicates while preserving order
137
+ seen = set()
138
+ unique_results = []
139
+ for result in results:
140
+ result_str = json.dumps(result, sort_keys=True)
141
+ if result_str not in seen:
142
+ seen.add(result_str)
143
+ unique_results.append(result)
144
+
145
+ return unique_results
146
+
147
+ def clean_and_validate_json(self, json_text: str) -> Optional[Dict[str, Any]]:
148
+ """Clean and validate JSON text."""
149
+ # Remove common formatting issues
150
+ cleaned = json_text.strip()
151
+
152
+ # Remove markdown code block markers
153
+ cleaned = re.sub(r'^```(?:json)?\s*', '', cleaned, flags=re.MULTILINE)
154
+ cleaned = re.sub(r'\s*```$', '', cleaned, flags=re.MULTILINE)
155
+
156
+ # Fix common JSON issues
157
+ cleaned = self._fix_common_json_issues(cleaned)
158
+
159
+ try:
160
+ return json.loads(cleaned)
161
+ except json.JSONDecodeError as e:
162
+ print(f"JSON validation failed: {e}")
163
+ return None
164
+
165
+ def _fix_common_json_issues(self, text: str) -> str:
166
+ """Fix common JSON formatting issues."""
167
+ # Fix single quotes to double quotes
168
+ text = re.sub(r"'([^']*)':", r'"\1":', text)
169
+ text = re.sub(r":\s*'([^']*)'", r': "\1"', text)
170
+
171
+ # Fix trailing commas
172
+ text = re.sub(r',\s*}', '}', text)
173
+ text = re.sub(r',\s*]', ']', text)
174
+
175
+ # Fix missing commas between object properties
176
+ text = re.sub(r'"\s*\n\s*"', '",\n"', text)
177
+
178
+ # Fix unquoted keys
179
+ text = re.sub(r'(\w+):', r'"\1":', text)
180
+
181
+ return text
182
+
183
+
184
+ # Pre-built extraction tasks
185
+ @json_extraction_task(
186
+ name="extract_json_from_text",
187
+ description="Extract JSON objects from any text content"
188
+ )
189
+ async def extract_json_from_text(text: str, strategy: str = "hybrid") -> List[Dict[str, Any]]:
190
+ """Extract JSON objects from text."""
191
+ extractor = JSONExtractor()
192
+ strategy_enum = ExtractionStrategy(strategy) if strategy in [s.value for s in ExtractionStrategy] else ExtractionStrategy.HYBRID
193
+ return extractor.extract_json_from_text(text, strategy_enum)
194
+
195
+
196
+ @json_extraction_task(
197
+ name="extract_structured_data",
198
+ description="Extract structured data using LLM guidance",
199
+ schema={
200
+ "type": "object",
201
+ "properties": {
202
+ "extracted_data": {"type": "array"},
203
+ "confidence": {"type": "number"},
204
+ "method": {"type": "string"}
205
+ },
206
+ "required": ["extracted_data"]
207
+ }
208
+ )
209
+ async def extract_structured_data(
210
+ text: str,
211
+ data_description: str,
212
+ agent: Optional['Agent'] = None
213
+ ) -> Dict[str, Any]:
214
+ """
215
+ Extract structured data using LLM guidance.
216
+
217
+ Args:
218
+ text: Input text to extract data from
219
+ data_description: Description of what data to extract
220
+ agent: Optional agent to use for extraction (uses default if not provided)
221
+
222
+ Returns:
223
+ Dictionary with extracted data, confidence score, and method used
224
+ """
225
+ from .agent import Agent
226
+
227
+ # First try regex-based extraction
228
+ extractor = JSONExtractor()
229
+ regex_results = extractor.extract_json_from_text(text, ExtractionStrategy.REGEX)
230
+
231
+ if regex_results:
232
+ return {
233
+ "extracted_data": regex_results,
234
+ "confidence": 0.8,
235
+ "method": "regex"
236
+ }
237
+
238
+ # If no results, use LLM guidance
239
+ if agent is None:
240
+ agent = Agent(name="data_extractor", model="gpt-4o-mini")
241
+
242
+ prompt = f"""Extract structured data from the following text.
243
+
244
+ Data to extract: {data_description}
245
+
246
+ Text:
247
+ {text}
248
+
249
+ Please return the extracted data as a JSON object. If multiple items are found, return them as an array.
250
+ Focus on accuracy and completeness. If no relevant data is found, return an empty object.
251
+
252
+ Example format:
253
+ {{
254
+ "item1": {{"field1": "value1", "field2": "value2"}},
255
+ "item2": {{"field1": "value3", "field2": "value4"}}
256
+ }}
257
+
258
+ Extracted data:"""
259
+
260
+ try:
261
+ response = await agent.run(prompt)
262
+
263
+ # Extract JSON from agent response
264
+ llm_results = extractor.extract_json_from_text(response.content, ExtractionStrategy.HYBRID)
265
+
266
+ return {
267
+ "extracted_data": llm_results if llm_results else [],
268
+ "confidence": 0.9 if llm_results else 0.1,
269
+ "method": "llm_guided"
270
+ }
271
+ except Exception as e:
272
+ return {
273
+ "extracted_data": [],
274
+ "confidence": 0.0,
275
+ "method": "failed",
276
+ "error": str(e)
277
+ }
278
+
279
+
280
+ @json_extraction_task(
281
+ name="clean_and_validate_json",
282
+ description="Clean and validate JSON text for proper formatting"
283
+ )
284
+ async def clean_and_validate_json(json_text: str) -> Dict[str, Any]:
285
+ """Clean and validate JSON text."""
286
+ extractor = JSONExtractor()
287
+ result = extractor.clean_and_validate_json(json_text)
288
+
289
+ if result is None:
290
+ raise ValueError("Invalid JSON that cannot be cleaned")
291
+
292
+ return result
293
+
294
+
295
+ @task(
296
+ name="extract_entities",
297
+ description="Extract named entities and their relationships",
298
+ output_format=OutputFormat.JSON,
299
+ output_schema={
300
+ "type": "object",
301
+ "properties": {
302
+ "entities": {
303
+ "type": "array",
304
+ "items": {
305
+ "type": "object",
306
+ "properties": {
307
+ "text": {"type": "string"},
308
+ "type": {"type": "string"},
309
+ "confidence": {"type": "number"}
310
+ }
311
+ }
312
+ },
313
+ "relationships": {
314
+ "type": "array",
315
+ "items": {
316
+ "type": "object",
317
+ "properties": {
318
+ "source": {"type": "string"},
319
+ "target": {"type": "string"},
320
+ "relation": {"type": "string"}
321
+ }
322
+ }
323
+ }
324
+ }
325
+ }
326
+ )
327
+ async def extract_entities(text: str, entity_types: Optional[List[str]] = None) -> Dict[str, Any]:
328
+ """
329
+ Extract named entities from text.
330
+
331
+ Args:
332
+ text: Input text
333
+ entity_types: Optional list of entity types to focus on
334
+
335
+ Returns:
336
+ Dictionary with entities and relationships
337
+ """
338
+ # This is a simplified implementation
339
+ # In a real scenario, you'd use NLP libraries like spaCy or use LLM for extraction
340
+
341
+ entities = []
342
+ relationships = []
343
+
344
+ # Simple regex-based entity extraction (can be enhanced)
345
+ patterns = {
346
+ "PERSON": r'\b[A-Z][a-z]+ [A-Z][a-z]+\b',
347
+ "EMAIL": r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
348
+ "PHONE": r'\b\d{3}-\d{3}-\d{4}\b|\b\(\d{3}\) \d{3}-\d{4}\b',
349
+ "DATE": r'\b\d{1,2}/\d{1,2}/\d{4}\b|\b\d{4}-\d{2}-\d{2}\b',
350
+ }
351
+
352
+ for entity_type, pattern in patterns.items():
353
+ if entity_types is None or entity_type in entity_types:
354
+ matches = re.finditer(pattern, text)
355
+ for match in matches:
356
+ entities.append({
357
+ "text": match.group(),
358
+ "type": entity_type,
359
+ "confidence": 0.8 # Simple confidence score
360
+ })
361
+
362
+ return {
363
+ "entities": entities,
364
+ "relationships": relationships
365
+ }
366
+
367
+
368
+ # Utility functions for common extraction patterns
369
+ def extract_urls(text: str) -> List[str]:
370
+ """Extract URLs from text."""
371
+ url_pattern = r'https?://(?:[-\w.])+(?:[:\d]+)?(?:/(?:[\w/_.])*(?:\?(?:[\w&=%.])*)?(?:#(?:[\w.])*)?)?'
372
+ return re.findall(url_pattern, text)
373
+
374
+
375
+ def extract_emails(text: str) -> List[str]:
376
+ """Extract email addresses from text."""
377
+ email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
378
+ return re.findall(email_pattern, text)
379
+
380
+
381
+ def extract_phone_numbers(text: str) -> List[str]:
382
+ """Extract phone numbers from text."""
383
+ phone_patterns = [
384
+ r'\b\d{3}-\d{3}-\d{4}\b',
385
+ r'\b\(\d{3}\) \d{3}-\d{4}\b',
386
+ r'\b\d{3}\.\d{3}\.\d{4}\b',
387
+ r'\b\d{10}\b'
388
+ ]
389
+
390
+ numbers = []
391
+ for pattern in phone_patterns:
392
+ numbers.extend(re.findall(pattern, text))
393
+
394
+ return numbers
395
+
396
+
397
+ def extract_dates(text: str) -> List[str]:
398
+ """Extract dates from text."""
399
+ date_patterns = [
400
+ r'\b\d{1,2}/\d{1,2}/\d{4}\b',
401
+ r'\b\d{4}-\d{2}-\d{2}\b',
402
+ r'\b\d{1,2}-\d{1,2}-\d{4}\b',
403
+ r'\b[A-Za-z]+ \d{1,2}, \d{4}\b'
404
+ ]
405
+
406
+ dates = []
407
+ for pattern in date_patterns:
408
+ dates.extend(re.findall(pattern, text))
409
+
410
+ return dates
agnt5/llm/__init__.py ADDED
@@ -0,0 +1,179 @@
1
+ """
2
+ LLM provider integrations for AGNT5 SDK.
3
+
4
+ Provides unified interface for different LLM providers (Anthropic, OpenAI, etc.)
5
+ with consistent message handling, tool calling, and streaming support.
6
+ """
7
+
8
+ from .base import (
9
+ LanguageModel,
10
+ LanguageModelResponse,
11
+ LanguageModelType,
12
+ Message,
13
+ Role,
14
+ TokenUsage,
15
+ ToolCall,
16
+ ToolResult,
17
+ LLMError,
18
+ )
19
+
20
+ # Model registry is available but not exported by default
21
+ # Users can import directly if needed: from agnt5.llm.model_registry import ...
22
+
23
+ try:
24
+ from .anthropic import AnthropicLanguageModel
25
+ ANTHROPIC_AVAILABLE = True
26
+ except ImportError:
27
+ ANTHROPIC_AVAILABLE = False
28
+
29
+ try:
30
+ from .openai import OpenAILanguageModel
31
+ OPENAI_AVAILABLE = True
32
+ except ImportError:
33
+ OPENAI_AVAILABLE = False
34
+
35
+ try:
36
+ from .google import GoogleLanguageModel
37
+ GOOGLE_AVAILABLE = True
38
+ except ImportError:
39
+ GOOGLE_AVAILABLE = False
40
+
41
+ try:
42
+ from .mistral import MistralLanguageModel
43
+ MISTRAL_AVAILABLE = True
44
+ except ImportError:
45
+ MISTRAL_AVAILABLE = False
46
+
47
+ try:
48
+ from .azure import AzureOpenAILanguageModel
49
+ AZURE_OPENAI_AVAILABLE = True
50
+ except ImportError:
51
+ AZURE_OPENAI_AVAILABLE = False
52
+
53
+ try:
54
+ from .together import TogetherAILanguageModel
55
+ TOGETHER_AVAILABLE = True
56
+ except ImportError:
57
+ TOGETHER_AVAILABLE = False
58
+
59
+ __all__ = [
60
+ # Base classes
61
+ "LanguageModel",
62
+ "LanguageModelResponse",
63
+ "LanguageModelType",
64
+ "Message",
65
+ "Role",
66
+ "TokenUsage",
67
+ "ToolCall",
68
+ "ToolResult",
69
+ "LLMError",
70
+
71
+ # Factory function
72
+ "create_llm",
73
+
74
+ # Provider availability flags
75
+ "ANTHROPIC_AVAILABLE",
76
+ "OPENAI_AVAILABLE",
77
+ "GOOGLE_AVAILABLE",
78
+ "MISTRAL_AVAILABLE",
79
+ "AZURE_OPENAI_AVAILABLE",
80
+ "TOGETHER_AVAILABLE",
81
+ ]
82
+
83
+ # Conditionally export providers
84
+ if ANTHROPIC_AVAILABLE:
85
+ __all__.append("AnthropicLanguageModel")
86
+
87
+ if OPENAI_AVAILABLE:
88
+ __all__.append("OpenAILanguageModel")
89
+
90
+ if GOOGLE_AVAILABLE:
91
+ __all__.append("GoogleLanguageModel")
92
+
93
+ if MISTRAL_AVAILABLE:
94
+ __all__.append("MistralLanguageModel")
95
+
96
+ if AZURE_OPENAI_AVAILABLE:
97
+ __all__.append("AzureOpenAILanguageModel")
98
+
99
+ if TOGETHER_AVAILABLE:
100
+ __all__.append("TogetherAILanguageModel")
101
+
102
+
103
+ def get_available_providers():
104
+ """Get list of available LLM providers."""
105
+ providers = []
106
+ if ANTHROPIC_AVAILABLE:
107
+ providers.append("anthropic")
108
+ if OPENAI_AVAILABLE:
109
+ providers.append("openai")
110
+ if GOOGLE_AVAILABLE:
111
+ providers.append("google")
112
+ if MISTRAL_AVAILABLE:
113
+ providers.append("mistral")
114
+ if AZURE_OPENAI_AVAILABLE:
115
+ providers.append("azure_openai")
116
+ if TOGETHER_AVAILABLE:
117
+ providers.append("together")
118
+ return providers
119
+
120
+
121
+ def create_llm(provider: str, model: str, **kwargs) -> LanguageModel:
122
+ """
123
+ Factory function to create LLM instances.
124
+
125
+ Args:
126
+ provider: Provider name ('anthropic', 'openai')
127
+ model: Model name
128
+ **kwargs: Provider-specific configuration
129
+
130
+ Returns:
131
+ LanguageModel instance
132
+
133
+ Raises:
134
+ ValueError: If provider is not available
135
+ """
136
+ if provider == "anthropic":
137
+ if not ANTHROPIC_AVAILABLE:
138
+ raise ValueError("Anthropic provider not available. Install: pip install anthropic")
139
+ return AnthropicLanguageModel(
140
+ llm_model=LanguageModelType.from_string(model),
141
+ **kwargs
142
+ )
143
+ elif provider == "openai":
144
+ if not OPENAI_AVAILABLE:
145
+ raise ValueError("OpenAI provider not available. Install: pip install openai")
146
+ return OpenAILanguageModel(
147
+ llm_model=LanguageModelType.from_string(model),
148
+ **kwargs
149
+ )
150
+ elif provider == "google":
151
+ if not GOOGLE_AVAILABLE:
152
+ raise ValueError("Google provider not available. Install: pip install google-generativeai")
153
+ return GoogleLanguageModel(
154
+ llm_model=LanguageModelType.from_string(model),
155
+ **kwargs
156
+ )
157
+ elif provider == "mistral":
158
+ if not MISTRAL_AVAILABLE:
159
+ raise ValueError("Mistral provider not available. Install: pip install openai")
160
+ return MistralLanguageModel(
161
+ llm_model=LanguageModelType.from_string(model),
162
+ **kwargs
163
+ )
164
+ elif provider == "azure_openai":
165
+ if not AZURE_OPENAI_AVAILABLE:
166
+ raise ValueError("Azure OpenAI provider not available. Install: pip install openai")
167
+ return AzureOpenAILanguageModel(
168
+ llm_model=LanguageModelType.from_string(model),
169
+ **kwargs
170
+ )
171
+ elif provider == "together":
172
+ if not TOGETHER_AVAILABLE:
173
+ raise ValueError("Together AI provider not available. Install: pip install openai")
174
+ return TogetherAILanguageModel(
175
+ llm_model=LanguageModelType.from_string(model),
176
+ **kwargs
177
+ )
178
+ else:
179
+ raise ValueError(f"Unknown provider: {provider}. Available: {get_available_providers()}")