lionagi 0.13.0__py3-none-any.whl → 0.13.2__py3-none-any.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.
@@ -122,13 +122,13 @@ class ModelParams(SchemaModel):
122
122
  for k, v in self.parameter_fields.items()
123
123
  if k in self._use_keys
124
124
  }
125
- params.update(
126
- {
127
- f.name: f.field_info
128
- for f in self.field_models
129
- if f.name in self._use_keys
130
- }
131
- )
125
+ # Add field_models with proper type annotations
126
+ for f in self.field_models:
127
+ if f.name in self._use_keys:
128
+ params[f.name] = f.field_info
129
+ # Set the annotation from the FieldModel's base_type
130
+ params[f.name].annotation = f.base_type
131
+
132
132
  return {k: (v.annotation, v) for k, v in params.items()}
133
133
 
134
134
  @field_validator("parameter_fields", mode="before")
@@ -281,9 +281,18 @@ class ModelParams(SchemaModel):
281
281
  self._validators = validators
282
282
 
283
283
  if self.field_descriptions:
284
+ # Update field_models with descriptions (create new instances since they're immutable)
285
+ updated_field_models = []
284
286
  for i in self.field_models:
285
287
  if i.name in self.field_descriptions:
286
- i.description = self.field_descriptions[i.name]
288
+ # Create new FieldModel with updated description
289
+ updated_field_model = i.with_description(
290
+ self.field_descriptions[i.name]
291
+ )
292
+ updated_field_models.append(updated_field_model)
293
+ else:
294
+ updated_field_models.append(i)
295
+ self.field_models = updated_field_models
287
296
 
288
297
  if not isinstance(self.name, str):
289
298
  if hasattr(self.base_type, "class_name"):
@@ -202,9 +202,10 @@ class OperableModel(HashableModel):
202
202
 
203
203
  if (
204
204
  field_name in self.extra_field_models
205
- and self.extra_field_models[field_name].validator is not UNDEFINED
205
+ and self.extra_field_models[field_name].has_validator()
206
206
  ):
207
- value = self.extra_field_models[field_name].validator(None, value)
207
+ # Use the validate method to check value - let validation errors propagate
208
+ self.extra_field_models[field_name].validate(value, field_name)
208
209
  if field_name in self.extra_fields:
209
210
  object.__setattr__(self, field_name, value)
210
211
  else:
@@ -264,6 +265,7 @@ class OperableModel(HashableModel):
264
265
  """
265
266
  a = {**self.model_fields, **self.extra_fields}
266
267
  a.pop("extra_fields", None)
268
+ a.pop("extra_field_models", None) # Exclude internal field tracking
267
269
  return a
268
270
 
269
271
  def add_field(
@@ -2,41 +2,139 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from pydantic import BaseModel, Field, PrivateAttr, model_validator
5
+ from typing import Any, Optional
6
+
7
+ from pydantic import BaseModel
6
8
  from pydantic.fields import FieldInfo
7
- from typing_extensions import Self
8
9
 
9
10
  from lionagi.libs.validate.fuzzy_match_keys import fuzzy_match_keys
10
- from lionagi.models import FieldModel, ModelParams, SchemaModel
11
+ from lionagi.models import FieldModel, ModelParams, OperableModel
11
12
  from lionagi.utils import UNDEFINED, to_json
12
13
 
13
14
 
14
- class Operative(SchemaModel):
15
- """Class representing an operative that handles request and response models for operations."""
15
+ class Operative:
16
+ """Class representing an operative that handles request and response models for operations.
17
+
18
+ This implementation uses OperableModel internally for better performance while
19
+ maintaining backward compatibility with the existing API.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ name: str | None = None,
25
+ request_type: type[BaseModel] | None = None,
26
+ response_type: type[BaseModel] | None = None,
27
+ response_model: BaseModel | None = None,
28
+ response_str_dict: dict | str | None = None,
29
+ auto_retry_parse: bool = True,
30
+ max_retries: int = 3,
31
+ parse_kwargs: dict | None = None,
32
+ request_params: (
33
+ ModelParams | None
34
+ ) = None, # Deprecated, for backward compatibility
35
+ **_kwargs, # Ignored for backward compatibility
36
+ ):
37
+ """Initialize the Operative.
38
+
39
+ Args:
40
+ name: Name of the operative
41
+ request_type: Pydantic model type for requests
42
+ response_type: Pydantic model type for responses
43
+ response_model: Current response model instance
44
+ response_str_dict: Raw response string/dict
45
+ auto_retry_parse: Whether to auto-retry parsing
46
+ max_retries: Maximum parse retries
47
+ parse_kwargs: Additional parse arguments
48
+ request_params: Deprecated - use direct field addition
49
+ response_params: Deprecated - use direct field addition
50
+ """
51
+ self.name = name
52
+ self.request_type = request_type
53
+ self.response_type = response_type
54
+ self.response_model = response_model
55
+ self.response_str_dict = response_str_dict
56
+ self.auto_retry_parse = auto_retry_parse
57
+ self.max_retries = max_retries
58
+ self.parse_kwargs = parse_kwargs or {}
59
+ self._should_retry = None
60
+
61
+ # Internal OperableModel instances
62
+ self._request_operable = OperableModel()
63
+ self._response_operable = OperableModel()
64
+
65
+ # Handle deprecated ModelParams for backward compatibility
66
+ if request_params:
67
+ self._init_from_model_params(request_params)
68
+
69
+ # Set default name if not provided
70
+ if not self.name:
71
+ self.name = (
72
+ self.request_type.__name__
73
+ if self.request_type
74
+ else "Operative"
75
+ )
76
+
77
+ def _init_from_model_params(self, params: ModelParams):
78
+ """Initialize from ModelParams for backward compatibility."""
79
+ # Add field models to the request operable
80
+ if params.field_models:
81
+ for field_model in params.field_models:
82
+ self._request_operable.add_field(
83
+ field_model.name,
84
+ field_model=field_model,
85
+ annotation=field_model.base_type,
86
+ )
87
+
88
+ # Add parameter fields (skip if already added from field_models)
89
+ if params.parameter_fields:
90
+ for name, field_info in params.parameter_fields.items():
91
+ if (
92
+ name not in (params.exclude_fields or [])
93
+ and name not in self._request_operable.all_fields
94
+ ):
95
+ self._request_operable.add_field(
96
+ name, field_obj=field_info
97
+ )
16
98
 
17
- name: str | None = None
99
+ # Generate request_type if not provided
100
+ if not self.request_type:
101
+ exclude_fields = params.exclude_fields or []
102
+ use_fields = set(self._request_operable.all_fields.keys()) - set(
103
+ exclude_fields
104
+ )
105
+ self.request_type = self._request_operable.new_model(
106
+ name=params.name or "RequestModel",
107
+ use_fields=use_fields,
108
+ base_type=params.base_type,
109
+ frozen=params.frozen,
110
+ config_dict=params.config_dict,
111
+ doc=params.doc,
112
+ )
18
113
 
19
- request_params: ModelParams | None = Field(default=None)
20
- request_type: type[BaseModel] | None = Field(default=None)
114
+ # Update name if not set
115
+ if not self.name and params.name:
116
+ self.name = params.name
21
117
 
22
- response_params: ModelParams | None = Field(default=None)
23
- response_type: type[BaseModel] | None = Field(default=None)
24
- response_model: BaseModel | None = Field(default=None)
25
- response_str_dict: dict | str | None = Field(default=None)
118
+ def model_dump(self) -> dict[str, Any]:
119
+ """Convert to dictionary for backward compatibility.
26
120
 
27
- auto_retry_parse: bool = True
28
- max_retries: int = 3
29
- parse_kwargs: dict | None = None
30
- _should_retry: bool = PrivateAttr(default=None)
121
+ Note: This returns a Python dict, not JSON-serializable data.
122
+ For JSON serialization, convert types appropriately.
123
+ """
124
+ return {
125
+ "name": self.name,
126
+ "request_type": self.request_type, # Python class object
127
+ "response_type": self.response_type, # Python class object
128
+ "response_model": self.response_model,
129
+ "response_str_dict": self.response_str_dict,
130
+ "auto_retry_parse": self.auto_retry_parse,
131
+ "max_retries": self.max_retries,
132
+ "parse_kwargs": self.parse_kwargs,
133
+ }
31
134
 
32
- @model_validator(mode="after")
33
- def _validate(self) -> Self:
34
- """Validates the operative instance after initialization."""
35
- if self.request_type is None:
36
- self.request_type = self.request_params.create_new_model()
37
- if self.name is None:
38
- self.name = self.request_params.name or self.request_type.__name__
39
- return self
135
+ def to_dict(self) -> dict[str, Any]:
136
+ """Alias for model_dump() - more appropriate name for non-Pydantic class."""
137
+ return self.model_dump()
40
138
 
41
139
  def raise_validate_pydantic(self, text: str) -> None:
42
140
  """Validates and updates the response model using strict matching.
@@ -153,18 +251,89 @@ class Operative(SchemaModel):
153
251
  frozen (bool, optional): Whether the model is frozen.
154
252
  validators (dict, optional): Dictionary of validators.
155
253
  """
156
- self.response_params = response_params or ModelParams(
157
- parameter_fields=parameter_fields,
158
- field_models=field_models,
159
- exclude_fields=exclude_fields,
160
- field_descriptions=field_descriptions,
161
- inherit_base=inherit_base,
254
+ # Process response_params if provided (for backward compatibility)
255
+ if response_params:
256
+ # Extract values from ModelParams
257
+ field_models = field_models or response_params.field_models
258
+ parameter_fields = (
259
+ parameter_fields or response_params.parameter_fields
260
+ )
261
+ exclude_fields = exclude_fields or response_params.exclude_fields
262
+ field_descriptions = (
263
+ field_descriptions or response_params.field_descriptions
264
+ )
265
+ inherit_base = (
266
+ response_params.inherit_base if inherit_base else False
267
+ )
268
+ config_dict = config_dict or response_params.config_dict
269
+ doc = doc or response_params.doc
270
+ frozen = frozen or response_params.frozen
271
+
272
+ # Clear response operable and rebuild
273
+ self._response_operable = OperableModel()
274
+
275
+ # Copy fields from request operable if inherit_base
276
+ if inherit_base and self._request_operable:
277
+ for (
278
+ field_name,
279
+ field_model,
280
+ ) in self._request_operable.extra_field_models.items():
281
+ self._response_operable.add_field(
282
+ field_name, field_model=field_model
283
+ )
284
+
285
+ # Add field models (skip if already exists from inheritance)
286
+ if field_models:
287
+ for field_model in field_models:
288
+ if field_model.name not in self._response_operable.all_fields:
289
+ self._response_operable.add_field(
290
+ field_model.name,
291
+ field_model=field_model,
292
+ annotation=field_model.base_type,
293
+ )
294
+
295
+ # Add parameter fields (skip if already added)
296
+ if parameter_fields:
297
+ for name, field_info in parameter_fields.items():
298
+ if (
299
+ name not in (exclude_fields or [])
300
+ and name not in self._response_operable.all_fields
301
+ ):
302
+ self._response_operable.add_field(
303
+ name, field_obj=field_info
304
+ )
305
+
306
+ # Add validators if provided
307
+ if validators:
308
+ for field_name, validator in validators.items():
309
+ if field_name in self._response_operable.all_fields:
310
+ field_model = (
311
+ self._response_operable.extra_field_models.get(
312
+ field_name
313
+ )
314
+ )
315
+ if field_model:
316
+ field_model.validator = validator
317
+
318
+ # Generate response type
319
+ exclude_fields = exclude_fields or []
320
+ use_fields = set(self._response_operable.all_fields.keys()) - set(
321
+ exclude_fields
322
+ )
323
+
324
+ # Determine base type - use request_type if inheriting and no specific base provided
325
+ base_type = None
326
+ if response_params and response_params.base_type:
327
+ base_type = response_params.base_type
328
+ elif inherit_base and self.request_type:
329
+ base_type = self.request_type
330
+
331
+ self.response_type = self._response_operable.new_model(
332
+ name=(response_params.name if response_params else None)
333
+ or "ResponseModel",
334
+ use_fields=use_fields,
335
+ base_type=base_type,
336
+ frozen=frozen,
162
337
  config_dict=config_dict,
163
338
  doc=doc,
164
- frozen=frozen,
165
- base_type=self.request_params.base_type,
166
339
  )
167
- if validators and isinstance(validators, dict):
168
- self.response_params._validators.update(validators)
169
-
170
- self.response_type = self.response_params.create_new_model()
@@ -230,7 +230,8 @@ class Step:
230
230
  field_models.extend([REASON_FIELD])
231
231
 
232
232
  exclude_fields = exclude_fields or []
233
- exclude_fields.extend(operative.request_params.exclude_fields)
233
+ # Note: We no longer have access to request_params.exclude_fields
234
+ # since Operative doesn't store ModelParams anymore
234
235
 
235
236
  operative.create_response_type(
236
237
  response_params=response_params,
@@ -10,11 +10,13 @@ from typing import Any, Literal
10
10
 
11
11
  from claude_code_sdk import ClaudeCodeOptions
12
12
  from claude_code_sdk import query as sdk_query
13
+ from claude_code_sdk import types as cc_types
13
14
  from pydantic import BaseModel, Field, field_validator, model_validator
14
15
 
16
+ from lionagi.libs.schema.as_readable import as_readable
15
17
  from lionagi.service.connections.endpoint import Endpoint
16
18
  from lionagi.service.connections.endpoint_config import EndpointConfig
17
- from lionagi.utils import to_dict
19
+ from lionagi.utils import to_dict, to_list
18
20
 
19
21
  # --------------------------------------------------------------------------- constants
20
22
  ClaudePermission = Literal[
@@ -67,6 +69,14 @@ class ClaudeCodeRequest(BaseModel):
67
69
  permission_prompt_tool_name: str | None = None
68
70
  disallowed_tools: list[str] = Field(default_factory=list)
69
71
 
72
+ # -- internal use --------------------------------------------------------
73
+ auto_finish: bool = Field(
74
+ default=True,
75
+ exclude=True,
76
+ description="Automatically finish the conversation after the first response",
77
+ )
78
+ verbose_output: bool = Field(default=False, exclude=True)
79
+
70
80
  # ------------------------ validators & helpers --------------------------
71
81
  @field_validator("permission_mode", mode="before")
72
82
  def _norm_perm(cls, v):
@@ -182,27 +192,46 @@ class ClaudeCodeRequest(BaseModel):
182
192
  if not messages:
183
193
  raise ValueError("messages may not be empty")
184
194
 
185
- prompt = messages[-1]["content"]
186
- if isinstance(prompt, (dict, list)):
187
- prompt = json.dumps(prompt)
195
+ prompt = ""
188
196
 
189
- if resume and continue_conversation is None:
197
+ # 1. if resume or continue_conversation, use the last message
198
+ if resume or continue_conversation:
190
199
  continue_conversation = True
191
-
200
+ prompt = messages[-1]["content"]
201
+ if isinstance(prompt, (dict, list)):
202
+ prompt = json.dumps(prompt)
203
+
204
+ # 2. else, use entire messages except system message
205
+ else:
206
+ prompts = []
207
+ continue_conversation = False
208
+ for message in messages:
209
+ if message["role"] != "system":
210
+ content = message["content"]
211
+ prompts.append(
212
+ json.dumps(content)
213
+ if isinstance(content, (dict, list))
214
+ else content
215
+ )
216
+
217
+ prompt = "\n".join(prompts)
218
+
219
+ # 3. assemble the request data
192
220
  data: dict[str, Any] = dict(
193
221
  prompt=prompt,
194
222
  resume=resume,
195
223
  continue_conversation=bool(continue_conversation),
196
224
  )
197
225
 
226
+ # 4. extract system prompt if available
198
227
  if (messages[0]["role"] == "system") and (
199
228
  resume or continue_conversation
200
229
  ):
201
230
  data["system_prompt"] = messages[0]["content"]
202
-
203
- # Merge optional system prompts
204
- if kwargs.get("system_prompt"):
205
- data["append_system_prompt"] = kwargs.pop("system_prompt")
231
+ if kwargs.get("append_system_prompt"):
232
+ data["append_system_prompt"] = str(
233
+ kwargs.get("append_system_prompt")
234
+ )
206
235
 
207
236
  data.update(kwargs)
208
237
  return cls.model_validate(data, strict=False)
@@ -238,7 +267,7 @@ class ClaudeCodeEndpoint(Endpoint):
238
267
  )
239
268
 
240
269
  async def stream(self, request: dict | BaseModel, **kwargs):
241
- payload = self.create_payload(request, **kwargs)["request"]
270
+ payload, _ = self.create_payload(request, **kwargs)["request"]
242
271
  async for chunk in self._stream_claude_code(payload):
243
272
  yield chunk
244
273
 
@@ -258,6 +287,7 @@ class ClaudeCodeEndpoint(Endpoint):
258
287
  "session_id": None,
259
288
  "model": "claude-code",
260
289
  "result": "",
290
+ "tool_uses": [],
261
291
  "tool_results": [],
262
292
  "is_error": False,
263
293
  "num_turns": None,
@@ -269,17 +299,31 @@ class ClaudeCodeEndpoint(Endpoint):
269
299
  },
270
300
  }
271
301
 
272
- from claude_code_sdk import types
273
-
274
302
  for response in responses:
275
- if isinstance(response, types.SystemMessage):
303
+ if isinstance(response, cc_types.SystemMessage):
276
304
  results["session_id"] = response.data.get("session_id")
277
305
  results["model"] = response.data.get("model", "claude-code")
278
- if isinstance(response, types.AssistantMessage):
279
- for block in response.content:
280
- if isinstance(block, types.TextBlock):
306
+ if isinstance(
307
+ response, cc_types.AssistantMessage | cc_types.UserMessage
308
+ ):
309
+ for block in to_list(
310
+ response.content,
311
+ flatten=True,
312
+ flatten_tuple_set=True,
313
+ dropna=True,
314
+ ):
315
+ if isinstance(block, cc_types.TextBlock):
281
316
  results["result"] += block.text.strip() + "\n"
282
- if isinstance(block, types.ToolResultBlock):
317
+
318
+ if isinstance(block, cc_types.ToolUseBlock):
319
+ entry = {
320
+ "id": block.id,
321
+ "name": block.name,
322
+ "input": block.input,
323
+ }
324
+ results["tool_uses"].append(entry)
325
+
326
+ if isinstance(block, cc_types.ToolResultBlock):
283
327
  results["tool_results"].append(
284
328
  {
285
329
  "tool_use_id": block.tool_use_id,
@@ -287,8 +331,10 @@ class ClaudeCodeEndpoint(Endpoint):
287
331
  "is_error": block.is_error,
288
332
  }
289
333
  )
290
- if isinstance(response, types.ResultMessage):
291
- results["result"] += response.result.strip() or ""
334
+
335
+ if isinstance(response, cc_types.ResultMessage):
336
+ if response.result:
337
+ results["result"] = str(response.result).strip()
292
338
  results["usage"] = response.usage
293
339
  results["is_error"] = response.is_error
294
340
  results["total_cost_usd"] = response.total_cost_usd
@@ -305,7 +351,135 @@ class ClaudeCodeEndpoint(Endpoint):
305
351
  **kwargs,
306
352
  ):
307
353
  responses = []
354
+ request: ClaudeCodeRequest = payload["request"]
355
+ system: cc_types.SystemMessage = None
356
+
357
+ # 1. stream the Claude Code response
308
358
  async for chunk in self._stream_claude_code(**payload):
359
+
360
+ if request.verbose_output:
361
+ _display_message(chunk)
362
+
363
+ if isinstance(chunk, cc_types.SystemMessage):
364
+ system = chunk
309
365
  responses.append(chunk)
310
366
 
367
+ # 2. If the last response is not a ResultMessage and auto_finish is True,
368
+ # we need to query Claude Code again to get the final result message.
369
+ if request.auto_finish and not isinstance(
370
+ responses[-1], cc_types.ResultMessage
371
+ ):
372
+ options = request.as_claude_options()
373
+ options.continue_conversation = True
374
+ options.max_turns = 1
375
+ if system:
376
+ options.resume = (
377
+ system.data.get("session_id", None) if system else None
378
+ )
379
+
380
+ async for chunk in sdk_query(
381
+ prompt="Please provide a the final result message only",
382
+ options=options,
383
+ ):
384
+ if isinstance(chunk, cc_types.ResultMessage):
385
+ if request.verbose_output:
386
+ str_ = _verbose_output(chunk)
387
+ if str_:
388
+ as_readable(
389
+ str_,
390
+ md=True,
391
+ display_str=True,
392
+ format_curly=True,
393
+ max_panel_width=100,
394
+ theme="light",
395
+ )
396
+
397
+ responses.append(chunk)
398
+
399
+ # 3. Parse the responses into a clean format
311
400
  return self._parse_claude_code_response(responses)
401
+
402
+
403
+ def _display_message(chunk):
404
+ if isinstance(
405
+ chunk,
406
+ cc_types.SystemMessage
407
+ | cc_types.AssistantMessage
408
+ | cc_types.UserMessage,
409
+ ):
410
+ str_ = _verbose_output(chunk)
411
+ if str_:
412
+ if str_.startswith("Claude:"):
413
+ as_readable(
414
+ str_,
415
+ md=True,
416
+ display_str=True,
417
+ max_panel_width=100,
418
+ theme="light",
419
+ )
420
+ else:
421
+ as_readable(
422
+ str_,
423
+ format_curly=True,
424
+ display_str=True,
425
+ max_panel_width=100,
426
+ theme="light",
427
+ )
428
+
429
+ if isinstance(chunk, cc_types.ResultMessage):
430
+ str_ = _verbose_output(chunk)
431
+ as_readable(
432
+ str_,
433
+ md=True,
434
+ display_str=True,
435
+ format_curly=True,
436
+ max_panel_width=100,
437
+ theme="light",
438
+ )
439
+
440
+
441
+ def _verbose_output(res: cc_types.Message) -> str:
442
+ str_ = ""
443
+ if isinstance(res, cc_types.SystemMessage):
444
+ str_ = f"Claude Code Session Started: {res.data.get('session_id', 'unknown')}"
445
+ str_ += f"\nModel: {res.data.get('model', 'claude-code')}\n---"
446
+ return str_
447
+
448
+ if isinstance(res, cc_types.AssistantMessage | cc_types.UserMessage):
449
+ for block in to_list(
450
+ res.content, flatten=True, flatten_tuple_set=True, dropna=True
451
+ ):
452
+ if isinstance(block, cc_types.TextBlock):
453
+ text = (
454
+ block.text.strip() if isinstance(block.text, str) else ""
455
+ )
456
+ str_ += f"Claude:\n{text}"
457
+
458
+ if isinstance(block, cc_types.ToolUseBlock):
459
+ input = (
460
+ json.dumps(block.input, indent=2)
461
+ if isinstance(block.input, dict)
462
+ else str(block.input)
463
+ )
464
+ input = input[:200] + "..." if len(input) > 200 else input
465
+ str_ += (
466
+ f"Tool Use: {block.name} - {block.id}\n - Input: {input}"
467
+ )
468
+
469
+ if isinstance(block, cc_types.ToolResultBlock):
470
+ content = str(block.content)
471
+ content = (
472
+ content[:200] + "..." if len(content) > 200 else content
473
+ )
474
+ str_ += (
475
+ f"Tool Result: {block.tool_use_id}\n - Content: {content}"
476
+ )
477
+ return str_
478
+
479
+ if isinstance(res, cc_types.ResultMessage):
480
+ str_ += f"Session Completion - {res.session_id}"
481
+ str_ += f"\nResult: {res.result or 'No result'}"
482
+ str_ += f"\n- Cost: ${res.total_cost_usd:.4f} USD"
483
+ str_ += f"\n- Duration: {res.duration_ms} ms (API: {res.duration_api_ms} ms)"
484
+ str_ += f"\n- Turns: {res.num_turns}"
485
+ return str_
@@ -53,7 +53,7 @@ OPENROUTER_CHAT_ENDPOINT_CONFIG = EndpointConfig(
53
53
  provider="openrouter",
54
54
  base_url="https://openrouter.ai/api/v1",
55
55
  endpoint="chat/completions",
56
- kwargs={"model": "google/gemini-2.5-flash-preview-05-20"},
56
+ kwargs={"model": "google/gemini-2.5-flash"},
57
57
  api_key=settings.OPENROUTER_API_KEY or "dummy-key-for-testing",
58
58
  auth_type="bearer",
59
59
  content_type="application/json",
@@ -61,18 +61,6 @@ OPENROUTER_CHAT_ENDPOINT_CONFIG = EndpointConfig(
61
61
  request_options=CreateChatCompletionRequest,
62
62
  )
63
63
 
64
- OPENROUTER_GEMINI_ENDPOINT_CONFIG = EndpointConfig(
65
- name="openrouter_gemini",
66
- provider="openrouter",
67
- base_url="https://openrouter.ai/api/v1",
68
- endpoint="chat/completions",
69
- kwargs={"model": "google/gemini-2.5-flash-preview-05-20"},
70
- api_key=settings.OPENROUTER_API_KEY or "dummy-key-for-testing",
71
- auth_type="bearer",
72
- content_type="application/json",
73
- method="POST",
74
- )
75
-
76
64
  OPENAI_EMBEDDING_ENDPOINT_CONFIG = EndpointConfig(
77
65
  name="openai_embed",
78
66
  provider="openai",