@@ -363,6 +363,90 @@ def get_global_keys(db: firestore.Client) -> dict[str, Any] | None:
363363 return None
364364
365365
366+ def _add_embedding_section (
367+ key_list : list [str ], filtered_global_keys : dict [str , Any ]
368+ ) -> str :
369+ """Build embedding section of .env file."""
370+ if not key_list :
371+ return ""
372+ content = "# Embedding model\n "
373+ for key in sorted (key_list ):
374+ content += f'{ key } ="{ filtered_global_keys [key ]} "\n '
375+ content += "\n "
376+ return content
377+
378+
379+ def _add_langfuse_section (
380+ key_list : list [str ],
381+ filtered_global_keys : dict [str , Any ],
382+ team_data : dict [str , Any ],
383+ ) -> str :
384+ """Build LangFuse section of .env file."""
385+ has_team_keys = team_data .get ("langfuse_secret_key" ) or team_data .get (
386+ "langfuse_public_key"
387+ )
388+ if not key_list and not has_team_keys :
389+ return ""
390+
391+ content = "# LangFuse\n "
392+ # Team-specific keys first
393+ if team_data .get ("langfuse_secret_key" ):
394+ content += f'LANGFUSE_SECRET_KEY="{ team_data .get ("langfuse_secret_key" , "" )} "\n '
395+ if team_data .get ("langfuse_public_key" ):
396+ content += f'LANGFUSE_PUBLIC_KEY="{ team_data .get ("langfuse_public_key" , "" )} "\n '
397+ # Then global keys
398+ for key in sorted (key_list ):
399+ content += f'{ key } ="{ filtered_global_keys [key ]} "\n '
400+ content += "\n "
401+ return content
402+
403+
404+ def _add_web_search_section (
405+ key_list : list [str ],
406+ filtered_global_keys : dict [str , Any ],
407+ team_data : dict [str , Any ],
408+ ) -> str :
409+ """Build Web Search section of .env file."""
410+ if not key_list and not team_data .get ("web_search_api_key" ):
411+ return ""
412+
413+ content = "# Web Search\n "
414+ # Global keys first
415+ for key in sorted (key_list ):
416+ content += f'{ key } ="{ filtered_global_keys [key ]} "\n '
417+ # Then team-specific key
418+ if team_data .get ("web_search_api_key" ):
419+ content += f'WEB_SEARCH_API_KEY="{ team_data .get ("web_search_api_key" , "" )} "\n '
420+ content += "\n "
421+ return content
422+
423+
424+ def _add_weaviate_section (
425+ key_list : list [str ], filtered_global_keys : dict [str , Any ]
426+ ) -> str :
427+ """Build Weaviate section of .env file."""
428+ if not key_list :
429+ return ""
430+ content = "# Weaviate\n "
431+ for key in sorted (key_list ):
432+ content += f'{ key } ="{ filtered_global_keys [key ]} "\n '
433+ content += "\n "
434+ return content
435+
436+
437+ def _add_other_keys_section (
438+ key_list : list [str ], filtered_global_keys : dict [str , Any ]
439+ ) -> str :
440+ """Build section for other uncategorized keys."""
441+ if not key_list :
442+ return ""
443+ content = "# Other Configuration\n "
444+ for key in sorted (key_list ):
445+ content += f'{ key } ="{ filtered_global_keys [key ]} "\n '
446+ content += "\n "
447+ return content
448+
449+
366450def create_env_file (
367451 output_path : Path ,
368452 team_data : dict [str , Any ],
@@ -386,54 +470,53 @@ def create_env_file(
386470 True if successful, False otherwise.
387471 """
388472 try :
473+ # Metadata fields to exclude from .env file
474+ metadata_fields = {"created_at" , "updated_at" }
475+
476+ # Filter out metadata fields from global keys
477+ filtered_global_keys = {
478+ k : v for k , v in global_keys .items () if k not in metadata_fields
479+ }
480+
481+ # Categorize global keys by prefix for organized output
482+ key_categories : dict [str , list [str ]] = {
483+ "EMBEDDING" : [],
484+ "LANGFUSE" : [],
485+ "WEAVIATE" : [],
486+ "WEB_SEARCH" : [],
487+ }
488+ other_keys = []
489+
490+ for key in filtered_global_keys :
491+ categorized = False
492+ for prefix , category_list in key_categories .items ():
493+ if key .startswith (prefix ):
494+ category_list .append (key )
495+ categorized = True
496+ break
497+ if not categorized :
498+ other_keys .append (key )
499+
389500 # Build .env content
390501 env_content = "#!/bin/bash\n "
391502 env_content += "# OpenAI-compatible LLM (Gemini)\n "
392503 env_content += 'OPENAI_BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai/"\n '
393504 env_content += f'OPENAI_API_KEY="{ team_data .get ("openai_api_key" , "" )} "\n \n '
394505
395- env_content += "# Embedding model\n "
396- env_content += (
397- f'EMBEDDING_BASE_URL="{ global_keys .get ("EMBEDDING_BASE_URL" , "" )} "\n '
398- )
399- env_content += (
400- f'EMBEDDING_API_KEY="{ global_keys .get ("EMBEDDING_API_KEY" , "" )} "\n \n '
401- )
402-
403- env_content += "# LangFuse\n "
404- env_content += (
405- f'LANGFUSE_SECRET_KEY="{ team_data .get ("langfuse_secret_key" , "" )} "\n '
406- )
407- env_content += (
408- f'LANGFUSE_PUBLIC_KEY="{ team_data .get ("langfuse_public_key" , "" )} "\n '
409- )
410- env_content += f'LANGFUSE_HOST="{ global_keys .get ("LANGFUSE_HOST" , "" )} "\n \n '
411-
412- env_content += "# Web Search\n "
413- env_content += (
414- f'WEB_SEARCH_API_KEY="{ team_data .get ("web_search_api_key" , "" )} "\n \n '
415- )
416-
417- env_content += "# Weaviate\n "
418- env_content += (
419- f'WEAVIATE_HTTP_HOST="{ global_keys .get ("WEAVIATE_HTTP_HOST" , "" )} "\n '
420- )
421- env_content += (
422- f'WEAVIATE_GRPC_HOST="{ global_keys .get ("WEAVIATE_GRPC_HOST" , "" )} "\n '
423- )
424- env_content += f'WEAVIATE_API_KEY="{ global_keys .get ("WEAVIATE_API_KEY" , "" )} "\n '
425- env_content += (
426- f'WEAVIATE_HTTP_PORT="{ global_keys .get ("WEAVIATE_HTTP_PORT" , "" )} "\n '
506+ # Add sections using helper functions
507+ env_content += _add_embedding_section (
508+ key_categories ["EMBEDDING" ], filtered_global_keys
427509 )
428- env_content += (
429- f'WEAVIATE_GRPC_PORT=" { global_keys . get ( "WEAVIATE_GRPC_PORT" , "" ) } " \n '
510+ env_content += _add_langfuse_section (
511+ key_categories [ "LANGFUSE" ], filtered_global_keys , team_data
430512 )
431- env_content += (
432- f'WEAVIATE_HTTP_SECURE=" { global_keys . get ( "WEAVIATE_HTTP_SECURE" , "" ) } " \n '
513+ env_content += _add_web_search_section (
514+ key_categories [ "WEB_SEARCH" ], filtered_global_keys , team_data
433515 )
434- env_content += (
435- f'WEAVIATE_GRPC_SECURE=" { global_keys . get ( "WEAVIATE_GRPC_SECURE" , "" ) } " \n '
516+ env_content += _add_weaviate_section (
517+ key_categories [ "WEAVIATE" ], filtered_global_keys
436518 )
519+ env_content += _add_other_keys_section (other_keys , filtered_global_keys )
437520
438521 # Write to file
439522 with open (output_path , "w" ) as f :
@@ -497,13 +580,17 @@ def validate_env_file(env_path: Path) -> tuple[bool, list[str]]:
497580 if not env_path .exists ():
498581 return False , ["File does not exist" ]
499582
583+ # Core required keys that must always be present
584+ # Note: This could be made more dynamic by fetching from Firestore,
585+ # but we maintain a minimal set here for basic validation
500586 required_keys = [
501587 "OPENAI_API_KEY" ,
502588 "EMBEDDING_BASE_URL" ,
503589 "EMBEDDING_API_KEY" ,
504590 "LANGFUSE_SECRET_KEY" ,
505591 "LANGFUSE_PUBLIC_KEY" ,
506592 "LANGFUSE_HOST" ,
593+ "WEB_SEARCH_BASE_URL" ,
507594 "WEB_SEARCH_API_KEY" ,
508595 "WEAVIATE_HTTP_HOST" ,
509596 "WEAVIATE_GRPC_HOST" ,
0 commit comments